UNPKG

@lightningjs/renderer

Version:
240 lines 9.52 kB
/* * If not stated otherwise in this file or this component's LICENSE file the * following copyright and licenses apply: * * Copyright 2023 Comcast Cable Communications Management, LLC. * * Licensed under the Apache License, Version 2.0 (the License); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Texture, TextureType } from './Texture.js'; import { isCompressedTextureContainer, loadCompressedTexture, } from '../lib/textureCompression.js'; import { convertUrlToAbsolute, dataURIToBlob, isBase64Image, } from '../lib/utils.js'; import { isSvgImage, loadSvg } from '../lib/textureSvg.js'; import { fetchJson } from '../text-rendering/font-face-types/utils.js'; /** * Texture consisting of an image loaded from a URL * * @remarks * The ImageTexture's {@link ImageTextureProps.src} prop defines the image URL * to be downloaded. * * By default, the texture's alpha values will be premultiplied into its color * values which is generally the desired setting before they are sent to the * texture's associated {@link Shader}. However, in special cases you may want * the Shader to receive straight (non-premultiplied) values. In that case you * can disable the default behavior by setting the * {@link ImageTextureProps.premultiplyAlpha} prop to `false`. */ export class ImageTexture extends Texture { props; type = TextureType.image; constructor(txManager, props) { super(txManager); this.props = ImageTexture.resolveDefaults(props); } hasAlphaChannel(mimeType) { return mimeType.indexOf('image/png') !== -1; } async loadImageFallback(src, hasAlpha) { const img = new Image(); if (typeof src === 'string' && isBase64Image(src) === false) { img.crossOrigin = 'anonymous'; } return new Promise((resolve) => { img.onload = () => { resolve({ data: img, premultiplyAlpha: hasAlpha }); }; img.onerror = () => { console.warn('Image loading failed, returning fallback object.'); resolve({ data: null, premultiplyAlpha: hasAlpha }); }; if (src instanceof Blob) { img.src = URL.createObjectURL(src); } else { img.src = src; } }); } async createImageBitmap(blob, premultiplyAlpha, sx, sy, sw, sh) { const hasAlphaChannel = premultiplyAlpha ?? blob.type.includes('image/png'); const imageBitmapSupported = this.txManager.imageBitmapSupported; if (imageBitmapSupported.full === true && sw !== null && sh !== null) { // createImageBitmap with crop const bitmap = await createImageBitmap(blob, sx || 0, sy || 0, sw, sh, { premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none', colorSpaceConversion: 'none', imageOrientation: 'none', }); return { data: bitmap, premultiplyAlpha: hasAlphaChannel }; } 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 return { data: await createImageBitmap(blob), premultiplyAlpha: hasAlphaChannel, }; } // default createImageBitmap without crop but with options const bitmap = await createImageBitmap(blob, { premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none', colorSpaceConversion: 'none', imageOrientation: 'none', }); return { data: bitmap, premultiplyAlpha: hasAlphaChannel }; } async loadImage(src) { const { premultiplyAlpha, sx, sy, sw, sh } = this.props; if (this.txManager.hasCreateImageBitmap === true) { if (isBase64Image(src) === false && this.txManager.hasWorker === true && this.txManager.imageWorkerManager !== null) { return this.txManager.imageWorkerManager.getImage(src, premultiplyAlpha, sx, sy, sw, sh); } let blob; if (isBase64Image(src) === true) { blob = dataURIToBlob(src); } else { blob = await fetchJson(src, 'blob').then((response) => response); } return this.createImageBitmap(blob, premultiplyAlpha, sx, sy, sw, sh); } return this.loadImageFallback(src, premultiplyAlpha ?? true); } async getTextureSource() { let resp; try { resp = await this.determineImageTypeAndLoadImage(); } catch (e) { this.setState('failed', e); return { data: null, }; } if (resp.data === null) { this.setState('failed', Error('ImageTexture: No image data')); return { data: null, }; } let width, height; // check if resp.data is typeof Uint8ClampedArray else // use resp.data.width and resp.data.height if (resp.data instanceof Uint8Array) { width = this.props.width ?? 0; height = this.props.height ?? 0; } else { width = resp.data?.width ?? (this.props.width || 0); height = resp.data?.height ?? (this.props.height || 0); } // we're loaded! this.setState('fetched', { width, height, }); return { data: resp.data, premultiplyAlpha: this.props.premultiplyAlpha ?? true, }; } determineImageTypeAndLoadImage() { const { src, premultiplyAlpha, type } = this.props; if (src === null) { return { data: null, }; } if (typeof src !== 'string') { if (src instanceof Blob) { if (this.txManager.hasCreateImageBitmap === true) { const { sx, sy, sw, sh } = this.props; return this.createImageBitmap(src, premultiplyAlpha, sx, sy, sw, sh); } else { return this.loadImageFallback(src, premultiplyAlpha ?? true); } } if (src instanceof ImageData) { return { data: src, premultiplyAlpha, }; } return { data: src(), premultiplyAlpha, }; } const absoluteSrc = convertUrlToAbsolute(src); if (type === 'regular') { return this.loadImage(absoluteSrc); } if (type === 'svg') { return loadSvg(absoluteSrc, this.props.width, this.props.height, this.props.sx, this.props.sy, this.props.sw, this.props.sh); } if (isSvgImage(src) === true) { return loadSvg(absoluteSrc, this.props.width, this.props.height, this.props.sx, this.props.sy, this.props.sw, this.props.sh); } if (type === 'compressed') { return loadCompressedTexture(absoluteSrc); } if (isCompressedTextureContainer(src) === true) { return loadCompressedTexture(absoluteSrc); } // default return this.loadImage(absoluteSrc); } /** * Generates a cache key for the ImageTexture based on the provided props. * @param props - The props used to generate the cache key. * @returns The cache key as a string, or `false` if the key cannot be generated. */ static makeCacheKey(props) { const resolvedProps = ImageTexture.resolveDefaults(props); // Only cache key-able textures; prioritise key const key = resolvedProps.key || resolvedProps.src; if (typeof key !== 'string') { return false; } // if we have source dimensions, cache the texture separately let dimensionProps = ''; if (resolvedProps.sh !== null && resolvedProps.sw !== null) { dimensionProps += ','; dimensionProps += resolvedProps.sx ?? ''; dimensionProps += resolvedProps.sy ?? ''; dimensionProps += resolvedProps.sw || ''; dimensionProps += resolvedProps.sh || ''; } return `ImageTexture,${key},${resolvedProps.premultiplyAlpha ?? 'true'}${dimensionProps}`; } static resolveDefaults(props) { return { src: props.src ?? '', premultiplyAlpha: props.premultiplyAlpha ?? true, // null, key: props.key ?? null, type: props.type ?? null, width: props.width ?? null, height: props.height ?? null, sx: props.sx ?? null, sy: props.sy ?? null, sw: props.sw ?? null, sh: props.sh ?? null, }; } static z$__type__Props; } //# sourceMappingURL=ImageTexture.js.map