Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@lightningjs/renderer

Package Overview
Dependencies
Maintainers
0
Versions
58
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@lightningjs/renderer - npm Package Compare versions

Comparing version 2.8.0 to 2.9.0-beta1

dist/tsconfig.tsbuildinfo

14

dist/src/core/CoreTextureManager.d.ts

@@ -9,2 +9,3 @@ import { ImageWorkerManager } from './lib/ImageWorker.js';

import type { Texture } from './textures/Texture.js';
import { EventEmitter } from '../common/EventEmitter.js';
/**

@@ -25,2 +26,7 @@ * Augmentable map of texture class types

}
export interface CreateImageBitmapSupport {
basic: boolean;
options: boolean;
full: boolean;
}
export type ExtractProps<Type> = Type extends {

@@ -113,3 +119,3 @@ z$__type__Props: infer Props;

}
export declare class CoreTextureManager {
export declare class CoreTextureManager extends EventEmitter {
/**

@@ -129,2 +135,7 @@ * Map of textures by cache key

hasCreateImageBitmap: boolean;
imageBitmapSupported: {
basic: boolean;
options: boolean;
full: boolean;
};
hasWorker: boolean;

@@ -150,2 +161,3 @@ /**

constructor(numImageWorkers: number);
private validateCreateImageBitmap;
registerTextureType<Type extends keyof TextureMap>(textureType: Type, textureClass: TextureMap[Type]): void;

@@ -152,0 +164,0 @@ loadTexture<Type extends keyof TextureMap>(textureType: Type, props: ExtractProps<TextureMap[Type]>): InstanceType<TextureMap[Type]>;

@@ -25,3 +25,4 @@ /*

import { RenderTexture } from './textures/RenderTexture.js';
export class CoreTextureManager {
import { EventEmitter } from '../common/EventEmitter.js';
export class CoreTextureManager extends EventEmitter {
/**

@@ -41,2 +42,7 @@ * Map of textures by cache key

hasCreateImageBitmap = !!self.createImageBitmap;
imageBitmapSupported = {
basic: false,
options: false,
full: false,
};
hasWorker = !!self.Worker;

@@ -62,9 +68,26 @@ /**

constructor(numImageWorkers) {
// Register default known texture types
if (this.hasCreateImageBitmap && this.hasWorker && numImageWorkers > 0) {
this.imageWorkerManager = new ImageWorkerManager(numImageWorkers);
}
if (!this.hasCreateImageBitmap) {
super();
this.validateCreateImageBitmap()
.then((result) => {
this.hasCreateImageBitmap =
result.basic || result.options || result.full;
this.imageBitmapSupported = result;
if (!this.hasCreateImageBitmap) {
console.warn('[Lightning] createImageBitmap is not supported on this browser. ImageTexture will be slower.');
}
if (this.hasCreateImageBitmap &&
this.hasWorker &&
numImageWorkers > 0) {
this.imageWorkerManager = new ImageWorkerManager(numImageWorkers, result);
}
else {
console.warn('[Lightning] Imageworker is 0 or not supported on this browser. Image loading will be slower.');
}
this.emit('initialized');
})
.catch((e) => {
console.warn('[Lightning] createImageBitmap is not supported on this browser. ImageTexture will be slower.');
}
// initialized without image worker manager and createImageBitmap
this.emit('initialized');
});
this.registerTextureType('ImageTexture', ImageTexture);

@@ -76,2 +99,135 @@ this.registerTextureType('ColorTexture', ColorTexture);

}
async validateCreateImageBitmap() {
// Test if createImageBitmap is supported using a simple 1x1 PNG image
// prettier-ignore (this is a binary PNG image)
const pngBinaryData = new Uint8Array([
0x89,
0x50,
0x4e,
0x47,
0x0d,
0x0a,
0x1a,
0x0a, // PNG signature
0x00,
0x00,
0x00,
0x0d, // IHDR chunk length
0x49,
0x48,
0x44,
0x52, // "IHDR" chunk type
0x00,
0x00,
0x00,
0x01, // Width: 1
0x00,
0x00,
0x00,
0x01, // Height: 1
0x01, // Bit depth: 1
0x03, // Color type: Indexed
0x00, // Compression method: Deflate
0x00, // Filter method: None
0x00, // Interlace method: None
0x25,
0xdb,
0x56,
0xca, // CRC for IHDR
0x00,
0x00,
0x00,
0x03, // PLTE chunk length
0x50,
0x4c,
0x54,
0x45, // "PLTE" chunk type
0x00,
0x00,
0x00, // Palette entry: Black
0xa7,
0x7a,
0x3d,
0xda, // CRC for PLTE
0x00,
0x00,
0x00,
0x01, // tRNS chunk length
0x74,
0x52,
0x4e,
0x53, // "tRNS" chunk type
0x00, // Transparency for black: Fully transparent
0x40,
0xe6,
0xd8,
0x66, // CRC for tRNS
0x00,
0x00,
0x00,
0x0a, // IDAT chunk length
0x49,
0x44,
0x41,
0x54, // "IDAT" chunk type
0x08,
0xd7, // Deflate header
0x63,
0x60,
0x00,
0x00,
0x00,
0x02,
0x00,
0x01, // Zlib-compressed data
0xe2,
0x21,
0xbc,
0x33, // CRC for IDAT
0x00,
0x00,
0x00,
0x00, // IEND chunk length
0x49,
0x45,
0x4e,
0x44, // "IEND" chunk type
0xae,
0x42,
0x60,
0x82, // CRC for IEND
]);
const support = {
basic: false,
options: false,
full: false,
};
// Test basic createImageBitmap support
const blob = new Blob([pngBinaryData], { type: 'image/png' });
const bitmap = await createImageBitmap(blob);
bitmap.close?.();
support.basic = true;
// Test createImageBitmap with options support
try {
const options = { premultiplyAlpha: 'none' };
const bitmapWithOptions = await createImageBitmap(blob, options);
bitmapWithOptions.close?.();
support.options = true;
}
catch (e) {
/* ignore */
}
// Test createImageBitmap with full options support
try {
const bitmapWithFullOptions = await createImageBitmap(blob, 0, 0, 1, 1, {
premultiplyAlpha: 'none',
});
bitmapWithFullOptions.close?.();
support.full = true;
}
catch (e) {
/* ignore */
}
return support;
}
registerTextureType(textureType, textureClass) {

@@ -78,0 +234,0 @@ this.txConstructors[textureType] = textureClass;

3

dist/src/core/lib/ImageWorker.d.ts

@@ -0,1 +1,2 @@

import type { CreateImageBitmapSupport } from '../CoreTextureManager.js';
import { type TextureData } from '../textures/Texture.js';

@@ -9,3 +10,3 @@ type MessageCallback = [(value: any) => void, (reason: any) => void];

nextId: number;
constructor(numImageWorkers: number);
constructor(numImageWorkers: number, createImageBitmapSupport: CreateImageBitmapSupport);
private handleMessage;

@@ -12,0 +13,0 @@ private createWorkers;

@@ -30,2 +30,4 @@ /*

function createImageWorker() {
var supportsOptionsCreateImageBitmap = false;
var supportsFullCreateImageBitmap = false;
function hasAlphaChannel(mimeType) {

@@ -47,3 +49,6 @@ return mimeType.indexOf('image/png') !== -1;

: hasAlphaChannel(blob.type);
if (width !== null && height !== null) {
// createImageBitmap with crop and options
if (supportsFullCreateImageBitmap === true &&
width !== null &&
height !== null) {
createImageBitmap(blob, x || 0, y || 0, width, height, {

@@ -62,7 +67,20 @@ premultiplyAlpha: withAlphaChannel ? 'premultiply' : 'none',

}
createImageBitmap(blob, {
premultiplyAlpha: withAlphaChannel ? 'premultiply' : 'none',
colorSpaceConversion: 'none',
imageOrientation: 'none',
})
// createImageBitmap without crop but with options
if (supportsOptionsCreateImageBitmap === true) {
createImageBitmap(blob, {
premultiplyAlpha: withAlphaChannel ? 'premultiply' : 'none',
colorSpaceConversion: 'none',
imageOrientation: 'none',
})
.then(function (data) {
resolve({ data, premultiplyAlpha: premultiplyAlpha });
})
.catch(function (error) {
reject(error);
});
return;
}
// Fallback for browsers that do not support createImageBitmap with options
// this is supported for Chrome v50 to v52/54 that doesn't support options
createImageBitmap(blob)
.then(function (data) {

@@ -105,4 +123,4 @@ resolve({ data, premultiplyAlpha: premultiplyAlpha });

nextId = 0;
constructor(numImageWorkers) {
this.workers = this.createWorkers(numImageWorkers);
constructor(numImageWorkers, createImageBitmapSupport) {
this.workers = this.createWorkers(numImageWorkers, createImageBitmapSupport);
this.workers.forEach((worker) => {

@@ -126,4 +144,9 @@ worker.onmessage = this.handleMessage.bind(this);

}
createWorkers(numWorkers = 1) {
const workerCode = `(${createImageWorker.toString()})()`;
createWorkers(numWorkers = 1, createImageBitmapSupport) {
let workerCode = `(${createImageWorker.toString()})()`;
// Replace placeholders with actual initialization values
const supportsOptions = createImageBitmapSupport.options ? 'true' : 'false';
const supportsFull = createImageBitmapSupport.full ? 'true' : 'false';
workerCode = workerCode.replace('var supportsOptionsCreateImageBitmap = false;', `var supportsOptionsCreateImageBitmap = ${supportsOptions};`);
workerCode = workerCode.replace('var supportsFullCreateImageBitmap = false;', `var supportsFullCreateImageBitmap = ${supportsFull};`);
const blob = new Blob([workerCode.replace('"use strict";', '')], {

@@ -135,3 +158,9 @@ type: 'application/javascript',

for (let i = 0; i < numWorkers; i++) {
workers.push(new Worker(blobURL));
const worker = new Worker(blobURL);
// Pass `createImageBitmap` support level during worker initialization
worker.postMessage({
type: 'init',
support: createImageBitmapSupport,
});
workers.push(worker);
}

@@ -138,0 +167,0 @@ return workers;

@@ -71,2 +71,4 @@ /**

readonly COLOR_ATTACHMENT0: 36064;
readonly INVALID_ENUM: number;
readonly INVALID_OPERATION: number;
constructor(gl: WebGLRenderingContext | WebGL2RenderingContext);

@@ -506,2 +508,10 @@ /**

* ```
* gl.getError(type);
* ```
*
* @returns
*/
getError(): number;
/**
* ```
* gl.createVertexArray();

@@ -508,0 +518,0 @@ * ```

@@ -81,2 +81,4 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */

COLOR_ATTACHMENT0;
INVALID_ENUM;
INVALID_OPERATION;
//#endregion WebGL Enums

@@ -154,2 +156,4 @@ constructor(gl) {

this.COLOR_ATTACHMENT0 = gl.COLOR_ATTACHMENT0;
this.INVALID_ENUM = gl.INVALID_ENUM;
this.INVALID_OPERATION = gl.INVALID_OPERATION;
}

@@ -785,2 +789,13 @@ /**

* ```
* gl.getError(type);
* ```
*
* @returns
*/
getError() {
const { gl } = this;
return gl.getError();
}
/**
* ```
* gl.createVertexArray();

@@ -787,0 +802,0 @@ * ```

@@ -23,3 +23,3 @@ /*

import { CanvasCoreTexture } from './CanvasCoreTexture.js';
import { getBorder, getRadius, strokeLine } from './internal/C2DShaderUtils.js';
import { getBorder, getRadius, roundRect, strokeLine, } from './internal/C2DShaderUtils.js';
import { formatRgba, parseColorRgba, parseColor, } from './internal/ColorUtils.js';

@@ -121,3 +121,3 @@ import { UnsupportedShader } from './shaders/UnsupportedShader.js';

const path = new Path2D();
path.roundRect(tx, ty, width, height, radius);
roundRect.call(path, tx, ty, width, height, radius);
ctx.clip(path);

@@ -171,3 +171,3 @@ }

if (radius) {
ctx.roundRect(tx + borderInnerWidth, ty + borderInnerWidth, width - borderWidth, height - borderWidth, radius);
roundRect.call(ctx, tx + borderInnerWidth, ty + borderInnerWidth, width - borderWidth, height - borderWidth, radius);
ctx.stroke();

@@ -174,0 +174,0 @@ }

@@ -12,3 +12,4 @@ import type { QuadOptions } from '../../CoreRenderer.js';

export declare function getBorder(quad: QuadOptions, direction?: '' | Direction): BorderEffectProps | undefined;
export declare function roundRect(this: CanvasRenderingContext2D | Path2D, x: number, y: number, width: number, height: number, radius: number | DOMPointInit | (number | DOMPointInit)[]): void;
export declare function strokeLine(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, lineWidth: number | undefined, color: number | undefined, direction: Direction): void;
export {};

@@ -61,2 +61,40 @@ /*

}
export function roundRect(x, y, width, height, radius) {
const context = Object.getPrototypeOf(this);
if (!context.roundRect) {
const fixOverlappingCorners = (radii) => {
const maxRadius = Math.min(width / 2, height / 2);
const totalHorizontal = radii.topLeft + radii.topRight + radii.bottomRight + radii.bottomLeft;
if (totalHorizontal > width || totalHorizontal > height) {
const scale = maxRadius /
Math.max(radii.topLeft, radii.topRight, radii.bottomRight, radii.bottomLeft);
radii.topLeft *= scale;
radii.topRight *= scale;
radii.bottomRight *= scale;
radii.bottomLeft *= scale;
}
};
const radii = typeof radius === 'number'
? {
topLeft: radius,
topRight: radius,
bottomRight: radius,
bottomLeft: radius,
}
: { topLeft: 0, topRight: 0, bottomRight: 0, bottomLeft: 0, ...radius };
fixOverlappingCorners(radii);
this.moveTo(x + radii.topLeft, y);
this.lineTo(x + width - radii.topRight, y);
this.ellipse(x + width - radii.topRight, y + radii.topRight, radii.topRight, radii.topRight, 0, 1.5 * Math.PI, 2 * Math.PI);
this.lineTo(x + width, y + height - radii.bottomRight);
this.ellipse(x + width - radii.bottomRight, y + height - radii.bottomRight, radii.bottomRight, radii.bottomRight, 0, 0, 0.5 * Math.PI);
this.lineTo(x + radii.bottomLeft, y + height);
this.ellipse(x + radii.bottomLeft, y + height - radii.bottomLeft, radii.bottomLeft, radii.bottomLeft, 0, 0.5 * Math.PI, Math.PI);
this.lineTo(x, y + radii.topLeft);
this.ellipse(x + radii.topLeft, y + radii.topLeft, radii.topLeft, radii.topLeft, 0, Math.PI, 1.5 * Math.PI);
}
else {
this.roundRect(x, y, width, height, radius);
}
}
export function strokeLine(ctx, x, y, width, height, lineWidth = 0, color, direction) {

@@ -63,0 +101,0 @@ if (!lineWidth) {

@@ -28,3 +28,3 @@ /*

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
MAX_VIEWPORT_DIMS: 0,
MAX_VIEWPORT_DIMS: 0, // Code below will replace this with an Int32Array
MAX_VERTEX_TEXTURE_IMAGE_UNITS: 0,

@@ -31,0 +31,0 @@ MAX_TEXTURE_IMAGE_UNITS: 0,

@@ -23,3 +23,4 @@ /*

if (!shader) {
throw new Error(`Unable to create shader type: ${type}. Source: ${source}`);
const glError = glw.getError();
throw new Error(`Unable to create the shader: ${type === glw.VERTEX_SHADER ? 'VERTEX_SHADER' : 'FRAGMENT_SHADER'}.${glError ? ` WebGlContext Error: ${glError}` : ''}`);
}

@@ -32,3 +33,3 @@ glw.shaderSource(shader, source);

}
console.log(glw.getShaderInfoLog(shader));
console.error(glw.getShaderInfoLog(shader));
glw.deleteShader(shader);

@@ -48,3 +49,3 @@ }

}
console.log(glw.getProgramInfoLog(program));
console.warn(glw.getProgramInfoLog(program));
glw.deleteProgram(program);

@@ -51,0 +52,0 @@ return undefined;

@@ -108,6 +108,6 @@ /*

name: 'a_position',
size: 2,
type: glw.FLOAT,
normalized: false,
stride,
size: 2, // 2 components per iteration
type: glw.FLOAT, // the data is 32bit floats
normalized: false, // don't normalize the data
stride, // 0 = move forward size * sizeof(type) each iteration to get the next position
offset: 0, // start at the beginning of the buffer

@@ -114,0 +114,0 @@ },

@@ -21,3 +21,2 @@ /*

import { WebGlCoreShader } from './WebGlCoreShader.js';
const MAX_TEXTURES = 8; // TODO: get from gl
/**

@@ -94,3 +93,3 @@ * Can render multiple quads with multiple textures (up to vertex shader texture limit)

const { x, y, width, height } = this.clippingRect;
const pixelRatio = options.pixelRatio;
const pixelRatio = this.parentHasRenderTexture ? 1 : options.pixelRatio;
const canvasHeight = options.canvas.height;

@@ -100,3 +99,10 @@ const clipX = Math.round(x * pixelRatio);

const clipHeight = Math.round(height * pixelRatio);
const clipY = Math.round(canvasHeight - clipHeight - y * pixelRatio);
let clipY = Math.round(canvasHeight - clipHeight - y * pixelRatio);
// if parent has render texture, we need to adjust the scissor rect
// to be relative to the parent's framebuffer
if (this.parentHasRenderTexture) {
clipY = this.framebufferDimensions
? this.framebufferDimensions.height - this.dimensions.height
: 0;
}
glw.setScissorTest(true);

@@ -103,0 +109,0 @@ glw.scissor(clipX, clipY, clipWidth, clipHeight);

@@ -82,3 +82,8 @@ /*

if (!vertexShader || !fragmentShader) {
throw new Error(`Unable to create shader type: ${glw.FRAGMENT_SHADER}. Source: ${fragmentSource}`);
throw new Error(`Unable to create the following shader(s): ${[
!vertexShader && 'VERTEX_SHADER',
!fragmentShader && 'FRAGMENT_SHADER',
]
.filter(Boolean)
.join(' and ')}`);
}

@@ -85,0 +90,0 @@ const program = createProgram(glw, vertexShader, fragmentShader);

@@ -418,3 +418,3 @@ /*

letterSpacing: props.letterSpacing ?? 0,
lineHeight: props.lineHeight,
lineHeight: props.lineHeight, // `undefined` is a valid value
maxLines: props.maxLines ?? 0,

@@ -421,0 +421,0 @@ textBaseline: props.textBaseline ?? 'alphabetic',

@@ -57,3 +57,5 @@ /*

// Pre-load it
this.texture.ctxTexture.load();
stage.txManager.once('initialized', () => {
this.texture.ctxTexture.load();
});
// Set this.data to the fetched data from dataUrl

@@ -66,3 +68,2 @@ fetch(atlasDataUrl)

// Add all the glyphs to the glyph map
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
let maxCharHeight = 0;

@@ -76,6 +77,4 @@ this.data.chars.forEach((glyph) => {

});
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
this.maxCharHeight = maxCharHeight;
// We know `data` is defined here, because we just set it
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.shaper = new SdfFontShaper(this.data, this.glyphMap);

@@ -82,0 +81,0 @@ // If the metrics aren't provided explicitly in the font face options,

@@ -446,6 +446,6 @@ /*

name: 'a_position',
size: 2,
type: glw.FLOAT,
normalized: false,
stride,
size: 2, // 2 components per iteration
type: glw.FLOAT, // the data is 32bit floats
normalized: false, // don't normalize the data
stride, // 0 = move forward size * sizeof(type) each iteration to get the next position
offset: 0, // start at the beginning of the buffer

@@ -452,0 +452,0 @@ },

@@ -116,3 +116,3 @@ /*

},
writable: false,
writable: false, // Prevents property from being changed
configurable: false, // Prevents property from being deleted

@@ -119,0 +119,0 @@ });

@@ -99,2 +99,10 @@ import type { CoreTextureManager } from '../CoreTextureManager.js';

hasAlphaChannel(mimeType: string): boolean;
loadImageFallback(src: string, hasAlpha: boolean): Promise<{
data: HTMLImageElement;
premultiplyAlpha: boolean;
}>;
createImageBitmap(blob: Blob, premultiplyAlpha: boolean | null, sx: number | null, sy: number | null, sw: number | null, sh: number | null): Promise<{
data: ImageBitmap | HTMLImageElement;
premultiplyAlpha: boolean;
}>;
loadImage(src: string): Promise<TextureData>;

@@ -101,0 +109,0 @@ getTextureData(): Promise<TextureData>;

@@ -47,47 +47,60 @@ /*

}
async loadImage(src) {
const { premultiplyAlpha, sx, sy, sw, sh, width, height } = this.props;
if (this.txManager.imageWorkerManager !== null) {
return await this.txManager.imageWorkerManager.getImage(src, premultiplyAlpha, sx, sy, sw, sh);
async loadImageFallback(src, hasAlpha) {
const img = new Image();
return new Promise((resolve) => {
img.onload = () => {
resolve({ data: img, premultiplyAlpha: hasAlpha });
};
img.onerror = () => {
console.warn('Image loading failed, returning fallback object.');
resolve({ data: img, premultiplyAlpha: hasAlpha });
};
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 &&
sx !== null &&
sy !== null &&
sw !== null &&
sh !== null) {
// createImageBitmap with crop
const bitmap = await createImageBitmap(blob, sx, sy, sw, sh, {
premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none',
colorSpaceConversion: 'none',
imageOrientation: 'none',
});
return { data: bitmap, premultiplyAlpha: hasAlphaChannel };
}
else if (this.txManager.hasCreateImageBitmap === true) {
const response = await fetch(src);
const blob = await response.blob();
const hasAlphaChannel = premultiplyAlpha ?? this.hasAlphaChannel(blob.type);
if (sw !== null && sh !== null) {
return {
data: await createImageBitmap(blob, sx ?? 0, sy ?? 0, sw, sh, {
premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none',
colorSpaceConversion: 'none',
imageOrientation: 'none',
}),
premultiplyAlpha: hasAlphaChannel,
};
}
else if (imageBitmapSupported.options === true) {
// createImageBitmap without crop but with options
const bitmap = await createImageBitmap(blob, {
premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none',
colorSpaceConversion: 'none',
imageOrientation: 'none',
});
return { data: bitmap, premultiplyAlpha: hasAlphaChannel };
}
else {
// 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 ? 'premultiply' : 'none',
colorSpaceConversion: 'none',
imageOrientation: 'none',
}),
data: await createImageBitmap(blob),
premultiplyAlpha: hasAlphaChannel,
};
}
else {
const img = new Image();
if (!src.startsWith('data:')) {
img.crossOrigin = 'Anonymous';
}
async loadImage(src) {
const { premultiplyAlpha, sx, sy, sw, sh } = this.props;
if (this.txManager.hasCreateImageBitmap === true) {
if (this.txManager.hasWorker === true &&
this.txManager.imageWorkerManager !== null) {
return this.txManager.imageWorkerManager.getImage(src, premultiplyAlpha, sx, sy, sw, sh);
}
img.src = src;
await new Promise((resolve, reject) => {
img.onload = () => resolve();
img.onerror = () => reject(new Error(`Failed to load image`));
}).catch((e) => {
console.error(e);
});
return {
data: img,
premultiplyAlpha: premultiplyAlpha ?? true,
};
const blob = await fetch(src).then((response) => response.blob());
return this.createImageBitmap(blob, premultiplyAlpha, sx, sy, sw, sh);
}
return this.loadImageFallback(src, premultiplyAlpha ?? true);
}

@@ -158,3 +171,3 @@ async getTextureData() {

src: props.src ?? '',
premultiplyAlpha: props.premultiplyAlpha ?? true,
premultiplyAlpha: props.premultiplyAlpha ?? true, // null,
key: props.key ?? null,

@@ -161,0 +174,0 @@ type: props.type ?? null,

@@ -15,3 +15,3 @@ /**

export declare const isPowerOfTwo: (value: number) => boolean | 0;
export declare const getTimingFunction: (str: string) => (time: number) => number | undefined;
export declare const getTimingFunction: (str: string) => ((time: number) => number | undefined);
/**

@@ -18,0 +18,0 @@ * Convert bytes to string of megabytes with 2 decimal points

import type { FpsUpdatePayload, FrameTickPayload } from '../common/CommonTypes.js';
import type { ShaderMap } from '../core/CoreShaderManager.js';
import type { INode, INodeWritableProps, ITextNode, ITextNodeWritableProps } from './INode.js';
import type { IShaderController } from './IShaderController.js';
import type { RendererMain, RendererMainSettings, SpecificShaderRef } from './RendererMain.js';
import type { RendererMain, RendererMainSettings } from './RendererMain.js';
/**

@@ -18,3 +16,2 @@ * This interface is to be implemented by Core Drivers

createTextNode(props: ITextNodeWritableProps): ITextNode;
createShaderController<ShType extends keyof ShaderMap>(shaderRef: SpecificShaderRef<ShType>): IShaderController;
destroyNode(node: INode): void;

@@ -21,0 +18,0 @@ getRootNode(): INode;

@@ -8,3 +8,2 @@ import type { ShaderMap } from '../core/CoreShaderManager.js';

import { EventEmitter } from '../common/EventEmitter.js';
import type { IShaderController } from './IShaderController.js';
/**

@@ -217,2 +216,6 @@ * An immutable reference to a specific Texture type

enableInspector?: boolean;
/**
* Renderer mode
*/
renderMode?: 'webgl' | 'canvas';
}

@@ -356,3 +359,3 @@ /**

*/
createShader<ShType extends keyof ShaderMap>(shaderType: ShType, props?: SpecificShaderRef<ShType>['props']): IShaderController;
createShader<ShType extends keyof ShaderMap>(shaderType: ShType, props?: SpecificShaderRef<ShType>['props']): SpecificShaderRef<ShType>;
/**

@@ -359,0 +362,0 @@ * Get a Node by its ID

@@ -88,2 +88,3 @@ /*

enableInspector: settings.enableInspector ?? false,
renderMode: settings.renderMode ?? 'webgl',
};

@@ -204,6 +205,6 @@ this.settings = resolvedSettings;

letterSpacing: props.letterSpacing ?? 0,
lineHeight: props.lineHeight ?? fontSize,
lineHeight: props.lineHeight, // `undefined` is a valid value
maxLines: props.maxLines ?? 0,
textBaseline: props.textBaseline ?? 'alphabetic',
verticalAlign: props.verticalAlign ?? 'top',
verticalAlign: props.verticalAlign ?? 'middle',
overflowSuffix: props.overflowSuffix ?? '...',

@@ -270,2 +271,3 @@ debug: props.debug ?? {},

rotation: props.rotation ?? 0,
rtt: props.rtt ?? false,
data: data,

@@ -334,7 +336,7 @@ };

createShader(shaderType, props) {
return this.driver.createShaderController({
return {
descType: 'shader',
shType: shaderType,
props: props,
});
};
}

@@ -341,0 +343,0 @@ /**

import type { ICoreDriver } from '../../main-api/ICoreDriver.js';
import type { INode, INodeWritableProps, ITextNodeWritableProps } from '../../main-api/INode.js';
import type { RendererMain, RendererMainSettings, SpecificShaderRef } from '../../main-api/RendererMain.js';
import type { RendererMain, RendererMainSettings } from '../../main-api/RendererMain.js';
import { MainOnlyTextNode } from './MainOnlyTextNode.js';
import type { FpsUpdatePayload, FrameTickPayload } from '../../common/CommonTypes.js';
import type { ShaderMap } from '../../core/CoreShaderManager.js';
import type { IShaderController } from '../../main-api/IShaderController.js';
export declare class MainCoreDriver implements ICoreDriver {

@@ -15,3 +13,2 @@ private root;

createTextNode(props: ITextNodeWritableProps): MainOnlyTextNode;
createShaderController<ShType extends keyof ShaderMap>(shaderRef: SpecificShaderRef<ShType>): IShaderController;
destroyNode(node: INode): void;

@@ -18,0 +15,0 @@ releaseTexture(id: number): void;

@@ -24,3 +24,2 @@ /*

import { loadCoreExtension } from '../utils.js';
import { MainOnlyShaderController } from './MainOnlyShaderController.js';
export class MainCoreDriver {

@@ -44,2 +43,3 @@ root = null;

numImageWorkers: rendererSettings.numImageWorkers,
renderMode: rendererSettings.renderMode,
debug: {

@@ -86,5 +86,2 @@ monitorTextureCache: false,

}
createShaderController(shaderRef) {
return new MainOnlyShaderController(shaderRef);
}
// TODO: Remove?

@@ -91,0 +88,0 @@ destroyNode(node) {

@@ -5,6 +5,5 @@ import type { CustomDataMap, INode, INodeAnimatableProps, INodeWritableProps } from '../../main-api/INode.js';

import { CoreNode } from '../../core/CoreNode.js';
import type { RendererMain, TextureRef } from '../../main-api/RendererMain.js';
import type { RendererMain, ShaderRef, TextureRef } from '../../main-api/RendererMain.js';
import type { AnimationSettings } from '../../core/animations/CoreAnimation.js';
import { EventEmitter } from '../../common/EventEmitter.js';
import type { MainOnlyShaderController } from './MainOnlyShaderController.js';
export declare function getNewId(): number;

@@ -15,3 +14,3 @@ export declare class MainOnlyNode extends EventEmitter implements INode {

readonly id: number;
readonly coreNode: CoreNode;
protected coreNode: CoreNode;
protected _children: MainOnlyNode[];

@@ -21,3 +20,3 @@ protected _src: string;

protected _texture: TextureRef | null;
protected _shader: MainOnlyShaderController | null;
protected _shader: ShaderRef | null;
protected _data: CustomDataMap | undefined;

@@ -88,2 +87,5 @@ constructor(props: INodeWritableProps, rendererMain: RendererMain, stage: Stage, coreNode?: CoreNode);

set texture(texture: TextureRef | null);
get rtt(): boolean;
set rtt(value: boolean);
get parentHasRenderTexture(): boolean;
private onTextureLoaded;

@@ -96,4 +98,4 @@ private onTextureFailed;

private onInViewport;
get shader(): MainOnlyShaderController | null;
set shader(shader: MainOnlyShaderController | null);
get shader(): ShaderRef | null;
set shader(shader: ShaderRef | null);
get data(): CustomDataMap | undefined;

@@ -100,0 +102,0 @@ set data(d: CustomDataMap);

@@ -82,2 +82,3 @@ /*

textureOptions: null,
rtt: props.rtt,
});

@@ -97,2 +98,3 @@ // Forward loaded/failed events

this.src = props.src;
this.rtt = props.rtt;
this._data = props.data;

@@ -116,2 +118,8 @@ }

set width(value) {
if (value !== this.coreNode.width && this.coreNode.rtt) {
this.texture = this.rendererMain.createTexture('RenderTexture', {
width: this.width,
height: this.height,
}, { preload: true, flipY: true });
}
this.coreNode.width = value;

@@ -123,2 +131,8 @@ }

set height(value) {
if (value !== this.coreNode.height && this.coreNode.rtt) {
this.texture = this.rendererMain.createTexture('RenderTexture', {
width: this.width,
height: this.height,
}, { preload: true, flipY: true });
}
this.coreNode.height = value;

@@ -335,2 +349,17 @@ }

}
get rtt() {
return this.coreNode.rtt;
}
set rtt(value) {
if (value) {
this.texture = this.rendererMain.createTexture('RenderTexture', {
width: this.width,
height: this.height,
}, { preload: true, flipY: true });
}
this.coreNode.rtt = value;
}
get parentHasRenderTexture() {
return this.coreNode.parentHasRenderTexture;
}
onTextureLoaded = (target, payload) => {

@@ -367,3 +396,3 @@ this.emit('loaded', payload);

if (shader) {
shader.attachNode(this);
this.coreNode.loadShader(shader.shType, shader.props);
}

@@ -370,0 +399,0 @@ }

@@ -7,3 +7,3 @@ import type { ITextNode, ITextNodeWritableProps } from '../../main-api/INode.js';

export declare class MainOnlyTextNode extends MainOnlyNode implements ITextNode {
readonly coreNode: CoreTextNode;
protected coreNode: CoreTextNode;
constructor(props: ITextNodeWritableProps, rendererMain: RendererMain, stage: Stage);

@@ -10,0 +10,0 @@ get text(): string;

@@ -78,2 +78,3 @@ /*

shaderProps: null,
rtt: false,
}));

@@ -163,5 +164,3 @@ }

set lineHeight(value) {
if (value) {
this.coreNode.lineHeight = value;
}
this.coreNode.lineHeight = value;
}

@@ -168,0 +167,0 @@ get maxLines() {

@@ -31,2 +31,3 @@ import { BufferStruct } from '@lightningjs/threadx';

rotation: number;
rtt: boolean;
}

@@ -91,2 +92,4 @@ export declare class NodeStruct extends BufferStruct implements NodeStructWritableProps {

set zIndexLocked(value: number);
get rtt(): boolean;
set rtt(value: boolean);
}

@@ -196,2 +196,8 @@ /*

}
get rtt() {
return false;
}
set rtt(value) {
// Decorator will handle this
}
}

@@ -282,2 +288,5 @@ __decorate([

], NodeStruct.prototype, "zIndexLocked", null);
__decorate([
structProp('boolean')
], NodeStruct.prototype, "rtt", null);
//# sourceMappingURL=NodeStruct.js.map

@@ -39,2 +39,3 @@ import type { NodeStruct, NodeStructWritableProps } from './NodeStruct.js';

zIndexLocked: number;
rtt: boolean;
}

@@ -57,2 +57,3 @@ /*

rotation: sharedNodeStruct.rotation,
rtt: sharedNodeStruct.rtt,
});

@@ -59,0 +60,0 @@ }

@@ -187,3 +187,5 @@ /*

__decorate([
structProp('number')
structProp('number', {
allowUndefined: true,
})
], TextNodeStruct.prototype, "lineHeight", null);

@@ -190,0 +192,0 @@ __decorate([

import type { INode, INodeWritableProps, ITextNode, ITextNodeWritableProps } from '../../main-api/INode.js';
import type { ICoreDriver } from '../../main-api/ICoreDriver.js';
import type { RendererMain, RendererMainSettings, SpecificShaderRef } from '../../main-api/RendererMain.js';
import type { RendererMain, RendererMainSettings } from '../../main-api/RendererMain.js';
import type { FpsUpdatePayload, FrameTickPayload } from '../../common/CommonTypes.js';
import type { IShaderController } from '../../main-api/IShaderController.js';
import type { ShaderMap } from '../../core/CoreShaderManager.js';
export interface ThreadXRendererSettings {

@@ -21,3 +19,2 @@ coreWorkerUrl: string;

createTextNode(props: ITextNodeWritableProps): ITextNode;
createShaderController<ShType extends keyof ShaderMap>(shaderRef: SpecificShaderRef<ShType>): IShaderController;
destroyNode(node: INode): void;

@@ -24,0 +21,0 @@ releaseTexture(textureDescId: number): void;

@@ -26,3 +26,2 @@ /*

import { ThreadXMainTextNode } from './ThreadXMainTextNode.js';
import { ThreadXMainShaderController } from './ThreadXMainShaderController.js';
export class ThreadXCoreDriver {

@@ -133,2 +132,3 @@ settings;

rotation: props.rotation,
rtt: props.rtt,
});

@@ -178,2 +178,3 @@ const node = new ThreadXMainNode(rendererMain, bufferStruct);

rotation: props.rotation,
rtt: props.rtt,
// Text specific properties

@@ -209,5 +210,2 @@ text: props.text,

}
createShaderController(shaderRef) {
return new ThreadXMainShaderController(shaderRef);
}
// TODO: Remove?

@@ -214,0 +212,0 @@ destroyNode(node) {

@@ -0,7 +1,8 @@

import { EventEmitter } from '../../common/EventEmitter.js';
import type { AnimationControllerState, IAnimationController } from '../../common/IAnimationController.js';
import type { ThreadXMainNode } from './ThreadXMainNode.js';
export declare class ThreadXMainAnimationController implements IAnimationController {
export declare class ThreadXMainAnimationController extends EventEmitter implements IAnimationController {
private node;
private id;
stoppedPromise: Promise<void> | null;
stoppedPromise: Promise<void>;
/**

@@ -11,4 +12,4 @@ * If this is null, then the animation is in a finished / stopped state.

stoppedResolve: (() => void) | null;
state: AnimationControllerState;
constructor(node: ThreadXMainNode, id: number);
state: AnimationControllerState;
start(): IAnimationController;

@@ -19,4 +20,7 @@ stop(): IAnimationController;

waitUntilStopped(): Promise<void>;
private onAnimationFinished;
private sendStart;
private sendStop;
private makeStoppedPromise;
private onFinished;
private onAnimating;
}

@@ -19,7 +19,9 @@ /*

*/
/* eslint-disable @typescript-eslint/unbound-method */
import { EventEmitter } from '../../common/EventEmitter.js';
import { assertTruthy } from '../../utils.js';
export class ThreadXMainAnimationController {
export class ThreadXMainAnimationController extends EventEmitter {
node;
id;
stoppedPromise = null;
stoppedPromise;
/**

@@ -29,25 +31,32 @@ * If this is null, then the animation is in a finished / stopped state.

stoppedResolve = null;
state;
constructor(node, id) {
super();
this.node = node;
this.id = id;
this.onAnimationFinished = this.onAnimationFinished.bind(this);
this.state = 'stopped';
// Initial stopped promise is resolved (since the animation is stopped)
this.stoppedPromise = Promise.resolve();
// Bind event handlers
this.onAnimating = this.onAnimating.bind(this);
this.onFinished = this.onFinished.bind(this);
}
state;
start() {
if (this.stoppedResolve === null) {
if (this.state !== 'running') {
this.makeStoppedPromise();
this.node.on('animationFinished', this.onAnimationFinished);
this.sendStart();
this.state = 'running';
}
this.state = 'running';
this.node.emit('startAnimation', { id: this.id });
return this;
}
stop() {
this.node.emit('stopAnimation', { id: this.id });
this.node.off('animationFinished', this.onAnimationFinished);
if (this.stoppedResolve !== null) {
this.stoppedResolve();
this.stoppedResolve = null;
if (this.state === 'stopped') {
return this;
}
this.sendStop();
// if (this.stoppedResolve !== null) {
// this.stoppedResolve();
// this.stoppedResolve = null;
// this.emit('stopped', this);
// }
this.state = 'stopped';

@@ -65,15 +74,18 @@ return this;

waitUntilStopped() {
this.makeStoppedPromise();
const promise = this.stoppedPromise;
assertTruthy(promise);
return promise;
return this.stoppedPromise;
}
onAnimationFinished(target, { id, loop }) {
if (id === this.id) {
this.node.off('animationFinished', this.onAnimationFinished);
this.stoppedResolve?.();
this.stoppedResolve = null;
this.state = 'stopped';
}
sendStart() {
// Hook up event listeners
this.node.on('animationFinished', this.onFinished);
this.node.on('animationAnimating', this.onAnimating);
// Then register the animation
this.node.emit('startAnimation', { id: this.id });
}
sendStop() {
// First unregister the animation
this.node.emit('stopAnimation', { id: this.id });
// Then unhook event listeners
this.node.off('animationFinished', this.onFinished);
this.node.off('animationAnimating', this.onAnimating);
}
makeStoppedPromise() {

@@ -86,3 +98,20 @@ if (this.stoppedResolve === null) {

}
onFinished(target, { id }) {
if (id === this.id) {
assertTruthy(this.stoppedResolve);
this.node.off('animationFinished', this.onFinished);
this.node.off('animationAnimating', this.onAnimating);
// resolve promise
this.stoppedResolve();
this.stoppedResolve = null;
this.emit('stopped', this);
this.state = 'stopped';
}
}
onAnimating(target, { id }) {
if (id === this.id) {
this.emit('animating', this);
}
}
}
//# sourceMappingURL=ThreadXMainAnimationController.js.map
import type { IAnimationController } from '../../common/IAnimationController.js';
import type { CustomDataMap, INode, INodeAnimatableProps } from '../../main-api/INode.js';
import type { RendererMain, TextureRef } from '../../main-api/RendererMain.js';
import type { RendererMain, ShaderRef, TextureRef } from '../../main-api/RendererMain.js';
import type { NodeStruct } from './NodeStruct.js';
import { SharedNode } from './SharedNode.js';
import type { AnimationSettings } from '../../core/animations/CoreAnimation.js';
import type { ThreadXMainShaderController } from './ThreadXMainShaderController.js';
export declare class ThreadXMainNode extends SharedNode implements INode {

@@ -14,5 +13,6 @@ private rendererMain;

protected _texture: TextureRef | null;
protected _shader: ThreadXMainShaderController | null;
protected _shader: ShaderRef | null;
protected _data: CustomDataMap | undefined;
private _src;
private _parentHasRenderTexture;
/**

@@ -31,4 +31,4 @@ * FinalizationRegistry for animation controllers. When an animation

set texture(texture: TextureRef | null);
get shader(): ThreadXMainShaderController | null;
set shader(shader: ThreadXMainShaderController | null);
get shader(): ShaderRef | null;
set shader(shader: ShaderRef | null);
get scale(): number | null;

@@ -41,2 +41,4 @@ set scale(scale: number | null);

set parent(newParent: ThreadXMainNode | null);
set parentHasRenderTexture(hasRenderTexture: boolean);
get parentHasRenderTexture(): boolean;
get children(): ThreadXMainNode[];

@@ -43,0 +45,0 @@ get props(): this["z$__type__Props"];

@@ -32,2 +32,3 @@ /*

_src = '';
_parentHasRenderTexture = false;
/**

@@ -79,4 +80,3 @@ * FinalizationRegistry for animation controllers. When an animation

if (shader) {
shader.attachNode(this);
// this.emit('loadShader', shader as unknown as Record<string, unknown>);
this.emit('loadShader', shader);
}

@@ -138,2 +138,8 @@ }

}
set parentHasRenderTexture(hasRenderTexture) {
this._parentHasRenderTexture = hasRenderTexture;
}
get parentHasRenderTexture() {
return this._parentHasRenderTexture;
}
get children() {

@@ -140,0 +146,0 @@ return this._children;

@@ -73,2 +73,3 @@ /*

numImageWorkers: message.numImageWorkers,
renderMode: 'webgl',
debug: {

@@ -110,2 +111,3 @@ monitorTextureCache: false,

rotation: coreRootNode.rotation,
rtt: coreRootNode.rtt,
});

@@ -112,0 +114,0 @@ // Share the root node that was created by the Stage with the main worker.

@@ -55,2 +55,8 @@ /*

const animation = new CoreAnimation(this.coreNode, props, settings);
animation.on('animating', () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
this.emit('animationAnimating', {
id: id,
});
});
animation.on('finished', () => {

@@ -167,2 +173,3 @@ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access

rotation: sharedNodeStruct.rotation,
rtt: sharedNodeStruct.rtt,
// These are passed in via message handlers

@@ -169,0 +176,0 @@ shader: null,

@@ -55,2 +55,3 @@ /*

rotation: sharedNodeStruct.rotation,
rtt: sharedNodeStruct.rtt,
// These are passed in via message handlers

@@ -57,0 +58,0 @@ shader: null,

@@ -42,3 +42,8 @@ import { CoreExtension } from '../../exports/core-api.js';

export function santizeCustomDataMap(d) {
const validTypes = { boolean: true, string: true, number: true };
const validTypes = {
boolean: true,
string: true,
number: true,
undefined: true,
};
const keys = Object.keys(d);

@@ -45,0 +50,0 @@ for (let i = 0; i < keys.length; i++) {

{
"name": "@lightningjs/renderer",
"version": "2.8.0",
"version": "2.9.0-beta1",
"description": "Lightning 3 Renderer",

@@ -34,7 +34,7 @@ "type": "module",

"@types/node": "^20.0.0",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
"@vitest/coverage-v8": "^1.6.0",
"@typescript-eslint/eslint-plugin": "^8.16.0",
"@typescript-eslint/parser": "^8.16.0",
"@vitest/coverage-v8": "^2.1.5",
"concurrently": "^8.0.1",
"eslint": "^8.35.0",
"eslint": "^9.15.0",
"eslint-config-prettier": "^8.7.0",

@@ -44,6 +44,6 @@ "husky": "^8.0.3",

"prettier": "^2.8.4",
"typedoc": "^0.25.1",
"typescript": "^5.2.2",
"vitest": "^1.6.0",
"vitest-mock-extended": "^1.3.1"
"typedoc": "^0.26.11",
"typescript": "~5.6.3",
"vitest": "^2.0.0",
"vitest-mock-extended": "^2.0.2"
},

@@ -66,3 +66,2 @@ "lint-staged": {

],
"packageManager": "pnpm@8.9.2",
"engines": {

@@ -78,3 +77,3 @@ "npm": ">= 10.0.0",

"build": "tsc --build",
"build:docker": "docker build -t visual-regression .",
"build:docker": "cd visual-regression && pnpm build:docker",
"watch": "tsc --build --watch",

@@ -81,0 +80,0 @@ "test": "vitest",

@@ -6,5 +6,3 @@ # Lightning 3 Renderer

The Renderer is not designed for direct application development but instead
to provide a lightweight API for front-end application frameworks like Bolt and
Solid.
The Renderer is part of the [LightningJS](https://lightningjs.io) project. While it is possible to use the renderer directly, it is not recommended. Instead, Lightning 3 works best when combined with [Blits](https://lightningjs.io/v3-docs/blits/getting_started/intro.html).

@@ -49,2 +47,4 @@ ## Setup & Commands

For a more detailed and comprehensive list of browsers and their features please see [browsers](./BROWSERS.md).
## Example Tests

@@ -64,2 +64,6 @@

A hosted version can be found [here](https://lightning-js.github.io/renderer/).
This supports modern browsers as well as Chrome 38 and above through a legacy build.
See [examples/README.md](./examples/README.md) for more info.

@@ -147,2 +151,1 @@

| Canvas | N | Y |
| | | |

@@ -28,2 +28,3 @@ /*

import type { Texture } from './textures/Texture.js';
import { EventEmitter } from '../common/EventEmitter.js';

@@ -46,2 +47,8 @@ /**

export interface CreateImageBitmapSupport {
basic: boolean; // Supports createImageBitmap(image)
options: boolean; // Supports createImageBitmap(image, options)
full: boolean; // Supports createImageBitmap(image, sx, sy, sw, sh, options)
}
export type ExtractProps<Type> = Type extends { z$__type__Props: infer Props }

@@ -143,3 +150,3 @@ ? Props

export class CoreTextureManager {
export class CoreTextureManager extends EventEmitter {
/**

@@ -162,2 +169,8 @@ * Map of textures by cache key

hasCreateImageBitmap = !!self.createImageBitmap;
imageBitmapSupported = {
basic: false,
options: false,
full: false,
};
hasWorker = !!self.Worker;

@@ -185,13 +198,41 @@ /**

constructor(numImageWorkers: number) {
// Register default known texture types
if (this.hasCreateImageBitmap && this.hasWorker && numImageWorkers > 0) {
this.imageWorkerManager = new ImageWorkerManager(numImageWorkers);
}
super();
this.validateCreateImageBitmap()
.then((result) => {
this.hasCreateImageBitmap =
result.basic || result.options || result.full;
this.imageBitmapSupported = result;
if (!this.hasCreateImageBitmap) {
console.warn(
'[Lightning] createImageBitmap is not supported on this browser. ImageTexture will be slower.',
);
}
if (!this.hasCreateImageBitmap) {
console.warn(
'[Lightning] createImageBitmap is not supported on this browser. ImageTexture will be slower.',
);
}
if (
this.hasCreateImageBitmap &&
this.hasWorker &&
numImageWorkers > 0
) {
this.imageWorkerManager = new ImageWorkerManager(
numImageWorkers,
result,
);
} else {
console.warn(
'[Lightning] Imageworker is 0 or not supported on this browser. Image loading will be slower.',
);
}
this.emit('initialized');
})
.catch((e) => {
console.warn(
'[Lightning] createImageBitmap is not supported on this browser. ImageTexture will be slower.',
);
// initialized without image worker manager and createImageBitmap
this.emit('initialized');
});
this.registerTextureType('ImageTexture', ImageTexture);

@@ -204,2 +245,139 @@ this.registerTextureType('ColorTexture', ColorTexture);

private async validateCreateImageBitmap(): Promise<CreateImageBitmapSupport> {
// Test if createImageBitmap is supported using a simple 1x1 PNG image
// prettier-ignore (this is a binary PNG image)
const pngBinaryData = new Uint8Array([
0x89,
0x50,
0x4e,
0x47,
0x0d,
0x0a,
0x1a,
0x0a, // PNG signature
0x00,
0x00,
0x00,
0x0d, // IHDR chunk length
0x49,
0x48,
0x44,
0x52, // "IHDR" chunk type
0x00,
0x00,
0x00,
0x01, // Width: 1
0x00,
0x00,
0x00,
0x01, // Height: 1
0x01, // Bit depth: 1
0x03, // Color type: Indexed
0x00, // Compression method: Deflate
0x00, // Filter method: None
0x00, // Interlace method: None
0x25,
0xdb,
0x56,
0xca, // CRC for IHDR
0x00,
0x00,
0x00,
0x03, // PLTE chunk length
0x50,
0x4c,
0x54,
0x45, // "PLTE" chunk type
0x00,
0x00,
0x00, // Palette entry: Black
0xa7,
0x7a,
0x3d,
0xda, // CRC for PLTE
0x00,
0x00,
0x00,
0x01, // tRNS chunk length
0x74,
0x52,
0x4e,
0x53, // "tRNS" chunk type
0x00, // Transparency for black: Fully transparent
0x40,
0xe6,
0xd8,
0x66, // CRC for tRNS
0x00,
0x00,
0x00,
0x0a, // IDAT chunk length
0x49,
0x44,
0x41,
0x54, // "IDAT" chunk type
0x08,
0xd7, // Deflate header
0x63,
0x60,
0x00,
0x00,
0x00,
0x02,
0x00,
0x01, // Zlib-compressed data
0xe2,
0x21,
0xbc,
0x33, // CRC for IDAT
0x00,
0x00,
0x00,
0x00, // IEND chunk length
0x49,
0x45,
0x4e,
0x44, // "IEND" chunk type
0xae,
0x42,
0x60,
0x82, // CRC for IEND
]);
const support: CreateImageBitmapSupport = {
basic: false,
options: false,
full: false,
};
// Test basic createImageBitmap support
const blob = new Blob([pngBinaryData], { type: 'image/png' });
const bitmap = await createImageBitmap(blob);
bitmap.close?.();
support.basic = true;
// Test createImageBitmap with options support
try {
const options = { premultiplyAlpha: 'none' as const };
const bitmapWithOptions = await createImageBitmap(blob, options);
bitmapWithOptions.close?.();
support.options = true;
} catch (e) {
/* ignore */
}
// Test createImageBitmap with full options support
try {
const bitmapWithFullOptions = await createImageBitmap(blob, 0, 0, 1, 1, {
premultiplyAlpha: 'none',
});
bitmapWithFullOptions.close?.();
support.full = true;
} catch (e) {
/* ignore */
}
return support;
}
registerTextureType<Type extends keyof TextureMap>(

@@ -206,0 +384,0 @@ textureType: Type,

@@ -20,2 +20,3 @@ /*

import type { CreateImageBitmapSupport } from '../CoreTextureManager.js';
import { type TextureData } from '../textures/Texture.js';

@@ -51,2 +52,5 @@

function createImageWorker() {
var supportsOptionsCreateImageBitmap = false;
var supportsFullCreateImageBitmap = false;
function hasAlphaChannel(mimeType: string) {

@@ -80,3 +84,8 @@ return mimeType.indexOf('image/png') !== -1;

if (width !== null && height !== null) {
// createImageBitmap with crop and options
if (
supportsFullCreateImageBitmap === true &&
width !== null &&
height !== null
) {
createImageBitmap(blob, x || 0, y || 0, width, height, {

@@ -96,7 +105,21 @@ premultiplyAlpha: withAlphaChannel ? 'premultiply' : 'none',

createImageBitmap(blob, {
premultiplyAlpha: withAlphaChannel ? 'premultiply' : 'none',
colorSpaceConversion: 'none',
imageOrientation: 'none',
})
// createImageBitmap without crop but with options
if (supportsOptionsCreateImageBitmap === true) {
createImageBitmap(blob, {
premultiplyAlpha: withAlphaChannel ? 'premultiply' : 'none',
colorSpaceConversion: 'none',
imageOrientation: 'none',
})
.then(function (data) {
resolve({ data, premultiplyAlpha: premultiplyAlpha });
})
.catch(function (error) {
reject(error);
});
return;
}
// Fallback for browsers that do not support createImageBitmap with options
// this is supported for Chrome v50 to v52/54 that doesn't support options
createImageBitmap(blob)
.then(function (data) {

@@ -147,4 +170,10 @@ resolve({ data, premultiplyAlpha: premultiplyAlpha });

constructor(numImageWorkers: number) {
this.workers = this.createWorkers(numImageWorkers);
constructor(
numImageWorkers: number,
createImageBitmapSupport: CreateImageBitmapSupport,
) {
this.workers = this.createWorkers(
numImageWorkers,
createImageBitmapSupport,
);
this.workers.forEach((worker) => {

@@ -169,5 +198,20 @@ worker.onmessage = this.handleMessage.bind(this);

private createWorkers(numWorkers = 1): Worker[] {
const workerCode = `(${createImageWorker.toString()})()`;
private createWorkers(
numWorkers = 1,
createImageBitmapSupport: CreateImageBitmapSupport,
): Worker[] {
let workerCode = `(${createImageWorker.toString()})()`;
// Replace placeholders with actual initialization values
const supportsOptions = createImageBitmapSupport.options ? 'true' : 'false';
const supportsFull = createImageBitmapSupport.full ? 'true' : 'false';
workerCode = workerCode.replace(
'var supportsOptionsCreateImageBitmap = false;',
`var supportsOptionsCreateImageBitmap = ${supportsOptions};`,
);
workerCode = workerCode.replace(
'var supportsFullCreateImageBitmap = false;',
`var supportsFullCreateImageBitmap = ${supportsFull};`,
);
const blob: Blob = new Blob([workerCode.replace('"use strict";', '')], {

@@ -179,3 +223,11 @@ type: 'application/javascript',

for (let i = 0; i < numWorkers; i++) {
workers.push(new Worker(blobURL));
const worker = new Worker(blobURL);
// Pass `createImageBitmap` support level during worker initialization
worker.postMessage({
type: 'init',
support: createImageBitmapSupport,
});
workers.push(worker);
}

@@ -182,0 +234,0 @@ return workers;

@@ -87,2 +87,4 @@ /* eslint-disable @typescript-eslint/no-unsafe-return */

public readonly COLOR_ATTACHMENT0;
public readonly INVALID_ENUM: number;
public readonly INVALID_OPERATION: number;
//#endregion WebGL Enums

@@ -179,2 +181,4 @@

this.COLOR_ATTACHMENT0 = gl.COLOR_ATTACHMENT0;
this.INVALID_ENUM = gl.INVALID_ENUM;
this.INVALID_OPERATION = gl.INVALID_OPERATION;
}

@@ -1027,2 +1031,14 @@ /**

* ```
* gl.getError(type);
* ```
*
* @returns
*/
getError() {
const { gl } = this;
return gl.getError();
}
/**
* ```
* gl.createVertexArray();

@@ -1029,0 +1045,0 @@ * ```

@@ -33,4 +33,9 @@ /*

import { CanvasCoreTexture } from './CanvasCoreTexture.js';
import { getBorder, getRadius, strokeLine } from './internal/C2DShaderUtils.js';
import {
getBorder,
getRadius,
roundRect,
strokeLine,
} from './internal/C2DShaderUtils.js';
import {
formatRgba,

@@ -170,3 +175,3 @@ parseColorRgba,

const path = new Path2D();
path.roundRect(tx, ty, width, height, radius);
roundRect.call(path, tx, ty, width, height, radius);
ctx.clip(path);

@@ -229,3 +234,4 @@ }

if (radius) {
ctx.roundRect(
roundRect.call(
ctx,
tx + borderInnerWidth,

@@ -232,0 +238,0 @@ ty + borderInnerWidth,

@@ -87,2 +87,95 @@ /*

export function roundRect(
this: CanvasRenderingContext2D | Path2D,
x: number,
y: number,
width: number,
height: number,
radius: number | DOMPointInit | (number | DOMPointInit)[],
) {
const context = Object.getPrototypeOf(this) as Path2D;
if (!context.roundRect) {
const fixOverlappingCorners = (radii: {
topLeft: number;
topRight: number;
bottomRight: number;
bottomLeft: number;
}) => {
const maxRadius = Math.min(width / 2, height / 2);
const totalHorizontal =
radii.topLeft + radii.topRight + radii.bottomRight + radii.bottomLeft;
if (totalHorizontal > width || totalHorizontal > height) {
const scale =
maxRadius /
Math.max(
radii.topLeft,
radii.topRight,
radii.bottomRight,
radii.bottomLeft,
);
radii.topLeft *= scale;
radii.topRight *= scale;
radii.bottomRight *= scale;
radii.bottomLeft *= scale;
}
};
const radii =
typeof radius === 'number'
? {
topLeft: radius,
topRight: radius,
bottomRight: radius,
bottomLeft: radius,
}
: { topLeft: 0, topRight: 0, bottomRight: 0, bottomLeft: 0, ...radius };
fixOverlappingCorners(radii);
this.moveTo(x + radii.topLeft, y);
this.lineTo(x + width - radii.topRight, y);
this.ellipse(
x + width - radii.topRight,
y + radii.topRight,
radii.topRight,
radii.topRight,
0,
1.5 * Math.PI,
2 * Math.PI,
);
this.lineTo(x + width, y + height - radii.bottomRight);
this.ellipse(
x + width - radii.bottomRight,
y + height - radii.bottomRight,
radii.bottomRight,
radii.bottomRight,
0,
0,
0.5 * Math.PI,
);
this.lineTo(x + radii.bottomLeft, y + height);
this.ellipse(
x + radii.bottomLeft,
y + height - radii.bottomLeft,
radii.bottomLeft,
radii.bottomLeft,
0,
0.5 * Math.PI,
Math.PI,
);
this.lineTo(x, y + radii.topLeft);
this.ellipse(
x + radii.topLeft,
y + radii.topLeft,
radii.topLeft,
radii.topLeft,
0,
Math.PI,
1.5 * Math.PI,
);
} else {
this.roundRect(x, y, width, height, radius);
}
}
export function strokeLine(

@@ -89,0 +182,0 @@ ctx: CanvasRenderingContext2D,

@@ -103,4 +103,10 @@ /*

if (!shader) {
throw new Error(`Unable to create shader type: ${type}. Source: ${source}`);
const glError = glw.getError();
throw new Error(
`Unable to create the shader: ${
type === glw.VERTEX_SHADER ? 'VERTEX_SHADER' : 'FRAGMENT_SHADER'
}.${glError ? ` WebGlContext Error: ${glError}` : ''}`,
);
}
glw.shaderSource(shader, source);

@@ -113,3 +119,3 @@ glw.compileShader(shader);

console.log(glw.getShaderInfoLog(shader));
console.error(glw.getShaderInfoLog(shader));
glw.deleteShader(shader);

@@ -136,5 +142,5 @@ }

console.log(glw.getProgramInfoLog(program));
console.warn(glw.getProgramInfoLog(program));
glw.deleteProgram(program);
return undefined;
}

@@ -26,7 +26,5 @@ /*

import type { Dimensions } from '../../../common/CommonTypes.js';
import type { Rect, RectWithValid } from '../../lib/utils.js';
import type { RectWithValid } from '../../lib/utils.js';
import type { WebGlContextWrapper } from '../../lib/WebGlContextWrapper.js';
const MAX_TEXTURES = 8; // TODO: get from gl
/**

@@ -99,3 +97,3 @@ * Can render multiple quads with multiple textures (up to vertex shader texture limit)

const { x, y, width, height } = this.clippingRect;
const pixelRatio = options.pixelRatio;
const pixelRatio = this.parentHasRenderTexture ? 1 : options.pixelRatio;
const canvasHeight = options.canvas.height;

@@ -106,3 +104,12 @@

const clipHeight = Math.round(height * pixelRatio);
const clipY = Math.round(canvasHeight - clipHeight - y * pixelRatio);
let clipY = Math.round(canvasHeight - clipHeight - y * pixelRatio);
// if parent has render texture, we need to adjust the scissor rect
// to be relative to the parent's framebuffer
if (this.parentHasRenderTexture) {
clipY = this.framebufferDimensions
? this.framebufferDimensions.height - this.dimensions.height
: 0;
}
glw.setScissorTest(true);

@@ -109,0 +116,0 @@ glw.scissor(clipX, clipY, clipWidth, clipHeight);

@@ -145,5 +145,11 @@ /*

);
if (!vertexShader || !fragmentShader) {
throw new Error(
`Unable to create shader type: ${glw.FRAGMENT_SHADER}. Source: ${fragmentSource}`,
`Unable to create the following shader(s): ${[
!vertexShader && 'VERTEX_SHADER',
!fragmentShader && 'FRAGMENT_SHADER',
]
.filter(Boolean)
.join(' and ')}`,
);

@@ -150,0 +156,0 @@ }

@@ -96,3 +96,5 @@ /*

// Pre-load it
this.texture.ctxTexture.load();
stage.txManager.once('initialized', () => {
this.texture.ctxTexture.load();
});

@@ -106,3 +108,3 @@ // Set this.data to the fetched data from dataUrl

// Add all the glyphs to the glyph map
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
let maxCharHeight = 0;

@@ -116,6 +118,6 @@ this.data.chars.forEach((glyph) => {

});
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
(this.maxCharHeight as number) = maxCharHeight;
// We know `data` is defined here, because we just set it
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
(this.shaper as FontShaper) = new SdfFontShaper(

@@ -122,0 +124,0 @@ this.data,

@@ -135,57 +135,90 @@ /*

async loadImage(src: string) {
const { premultiplyAlpha, sx, sy, sw, sh, width, height } = this.props;
async loadImageFallback(src: string, hasAlpha: boolean) {
const img = new Image();
if (this.txManager.imageWorkerManager !== null) {
return await this.txManager.imageWorkerManager.getImage(
src,
premultiplyAlpha,
sx,
sy,
sw,
sh,
);
} else if (this.txManager.hasCreateImageBitmap === true) {
const response = await fetch(src);
const blob = await response.blob();
const hasAlphaChannel =
premultiplyAlpha ?? this.hasAlphaChannel(blob.type);
return new Promise<{ data: HTMLImageElement; premultiplyAlpha: boolean }>(
(resolve) => {
img.onload = () => {
resolve({ data: img, premultiplyAlpha: hasAlpha });
};
if (sw !== null && sh !== null) {
return {
data: await createImageBitmap(blob, sx ?? 0, sy ?? 0, sw, sh, {
premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none',
colorSpaceConversion: 'none',
imageOrientation: 'none',
}),
premultiplyAlpha: hasAlphaChannel,
img.onerror = () => {
console.warn('Image loading failed, returning fallback object.');
resolve({ data: img, premultiplyAlpha: hasAlpha });
};
}
img.src = src;
},
);
}
async createImageBitmap(
blob: Blob,
premultiplyAlpha: boolean | null,
sx: number | null,
sy: number | null,
sw: number | null,
sh: number | null,
): Promise<{
data: ImageBitmap | HTMLImageElement;
premultiplyAlpha: boolean;
}> {
const hasAlphaChannel = premultiplyAlpha ?? blob.type.includes('image/png');
const imageBitmapSupported = this.txManager.imageBitmapSupported;
if (
imageBitmapSupported.full === true &&
sx !== null &&
sy !== null &&
sw !== null &&
sh !== null
) {
// createImageBitmap with crop
const bitmap = await createImageBitmap(blob, sx, sy, sw, sh, {
premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none',
colorSpaceConversion: 'none',
imageOrientation: 'none',
});
return { data: bitmap, premultiplyAlpha: hasAlphaChannel };
} else if (imageBitmapSupported.options === true) {
// createImageBitmap without crop but with options
const bitmap = await createImageBitmap(blob, {
premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none',
colorSpaceConversion: 'none',
imageOrientation: 'none',
});
return { data: bitmap, premultiplyAlpha: hasAlphaChannel };
} else {
// 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 ? 'premultiply' : 'none',
colorSpaceConversion: 'none',
imageOrientation: 'none',
}),
data: await createImageBitmap(blob),
premultiplyAlpha: hasAlphaChannel,
};
} else {
const img = new Image();
if (!src.startsWith('data:')) {
img.crossOrigin = 'Anonymous';
}
}
async loadImage(src: string) {
const { premultiplyAlpha, sx, sy, sw, sh } = this.props;
if (this.txManager.hasCreateImageBitmap === true) {
if (
this.txManager.hasWorker === true &&
this.txManager.imageWorkerManager !== null
) {
return this.txManager.imageWorkerManager.getImage(
src,
premultiplyAlpha,
sx,
sy,
sw,
sh,
);
}
img.src = src;
await new Promise<void>((resolve, reject) => {
img.onload = () => resolve();
img.onerror = () => reject(new Error(`Failed to load image`));
}).catch((e) => {
console.error(e);
});
return {
data: img,
premultiplyAlpha: premultiplyAlpha ?? true,
};
const blob = await fetch(src).then((response) => response.blob());
return this.createImageBitmap(blob, premultiplyAlpha, sx, sy, sw, sh);
}
return this.loadImageFallback(src, premultiplyAlpha ?? true);
}

@@ -192,0 +225,0 @@

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc