UNPKG

@lightningtv/renderer

Version:
362 lines (327 loc) 8.51 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 type { Vec4 } from '../renderers/webgl/internal/ShaderUtils.js'; export const PROTOCOL_REGEX = /^(data|ftps?|https?):/; export type RGBA = [r: number, g: number, b: number, a: number]; export const getNormalizedRgbaComponents = (rgba: number): RGBA => { const r = rgba >>> 24; const g = (rgba >>> 16) & 0xff; const b = (rgba >>> 8) & 0xff; const a = rgba & 0xff; return [r / 255, g / 255, b / 255, a / 255]; }; export const getRgbaComponents = (rgba: number): RGBA => { const r = rgba >>> 24; const g = (rgba >>> 16) & 0xff; const b = (rgba >>> 8) & 0xff; const a = rgba & 0xff; return [r, g, b, a]; }; export const norm = (rgba: number): number => { const r = rgba >>> 24; const g = (rgba >>> 16) & 0xff; const b = (rgba >>> 8) & 0xff; const a = rgba & 0xff; const rgbaArr: RGBA = [r / 255, g / 255, b / 255, a / 255]; rgbaArr[0] = Math.max(0, Math.min(255, rgbaArr[0])); rgbaArr[1] = Math.max(0, Math.min(255, rgbaArr[1])); rgbaArr[2] = Math.max(0, Math.min(255, rgbaArr[2])); rgbaArr[3] = Math.max(0, Math.min(255, rgbaArr[3])); let v = ((rgbaArr[3] | 0) << 24) + ((rgbaArr[0] | 0) << 16) + ((rgbaArr[1] | 0) << 8) + (rgbaArr[2] | 0); if (v < 0) { v = 0xffffffff + v + 1; } return v; }; export function getNormalizedAlphaComponent(rgba: number): number { return (rgba & 0xff) / 255.0; } /** * Get a CSS color string from a RGBA color * * @param color * @returns */ export function getRgbaString(color: RGBA) { const r = Math.floor(color[0] * 255.0); const g = Math.floor(color[1] * 255.0); const b = Math.floor(color[2] * 255.0); const a = Math.floor(color[3] * 255.0); return `rgba(${r},${g},${b},${a.toFixed(4)})`; } export interface Rect { x: number; y: number; width: number; height: number; } export interface RectWithValid extends Rect { valid: boolean; } export interface Bound { x1: number; y1: number; x2: number; y2: number; } export interface BoundWithValid extends Bound { valid: boolean; } export function createBound<T extends Bound = Bound>( x1: number, y1: number, x2: number, y2: number, out?: T, ): T { if (out) { out.x1 = x1; out.y1 = y1; out.x2 = x2; out.y2 = y2; return out; } return { x1, y1, x2, y2, } as T; } export function intersectBound<T extends Bound = Bound>( a: Bound, b: Bound, out?: T, ): T { const intersection = createBound( Math.max(a.x1, b.x1), Math.max(a.y1, b.y1), Math.min(a.x2, b.x2), Math.min(a.y2, b.y2), out, ); if (intersection.x1 < intersection.x2 && intersection.y1 < intersection.y2) { return intersection; } return createBound(0, 0, 0, 0, intersection); } export function boundsOverlap(a: Bound, b: Bound): boolean { return a.x1 < b.x2 && a.x2 > b.x1 && a.y1 < b.y2 && a.y2 > b.y1; } export function convertBoundToRect(bound: Bound): Rect; export function convertBoundToRect<T extends Rect = Rect>( bound: Bound, out: T, ): T; export function convertBoundToRect(bound: Bound, out?: Rect): Rect { if (out) { out.x = bound.x1; out.y = bound.y1; out.width = bound.x2 - bound.x1; out.height = bound.y2 - bound.y1; return out; } return { x: bound.x1, y: bound.y1, width: bound.x2 - bound.x1, height: bound.y2 - bound.y1, }; } export function intersectRect(a: Rect, b: Rect): Rect; export function intersectRect<T extends Rect = Rect>( a: Rect, b: Rect, out: T, ): T; export function intersectRect(a: Rect, b: Rect, out?: Rect): Rect { const x = Math.max(a.x, b.x); const y = Math.max(a.y, b.y); const width = Math.min(a.x + a.width, b.x + b.width) - x; const height = Math.min(a.y + a.height, b.y + b.height) - y; if (width > 0 && height > 0) { if (out) { out.x = x; out.y = y; out.width = width; out.height = height; return out; } return { x, y, width, height, }; } if (out) { out.x = 0; out.y = 0; out.width = 0; out.height = 0; return out; } return { x: 0, y: 0, width: 0, height: 0, }; } export function copyRect(a: Rect): Rect; export function copyRect<T extends Rect = Rect>(a: Rect, out: T): T; export function copyRect(a: Rect, out?: Rect): Rect { if (out) { out.x = a.x; out.y = a.y; out.width = a.width; out.height = a.height; return out; } return { x: a.x, y: a.y, width: a.width, height: a.height, }; } export function compareRect(a: Rect | null, b: Rect | null): boolean { if (a === b) { return true; } if (a === null || b === null) { return false; } return ( a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height ); } export function boundInsideBound(bound1: Bound, bound2: Bound) { return ( bound1.x1 <= bound2.x2 && bound1.y1 <= bound2.y2 && bound1.x2 >= bound2.x1 && bound1.y2 >= bound2.y1 ); } export function boundLargeThanBound(bound1: Bound, bound2: Bound) { return ( bound1.x1 < bound2.x1 && bound1.x2 > bound2.x2 && bound1.y1 < bound2.y1 && bound1.y2 > bound2.y2 ); } export function isBoundPositive(bound: Bound): boolean { return bound.x1 < bound.x2 && bound.y1 < bound.y2; } export function isRectPositive(rect: Rect): boolean { return rect.width > 0 && rect.height > 0; } /** * Create a preload bounds from a strict bound * * @param strictBound The strict boundary of the node * @param boundsMargin Boundary margin to apply to the strictBound * @returns */ export function createPreloadBounds( strictBound: Bound, boundsMargin: [number, number, number, number], ): Bound { return createBound( strictBound.x1 - boundsMargin[3], strictBound.y1 - boundsMargin[0], strictBound.x2 + boundsMargin[1], strictBound.y2 + boundsMargin[2], ); } export function convertUrlToAbsolute(url: string): string { // handle local file imports if the url isn't remote resource or data blob if (self.location.protocol === 'file:' && !PROTOCOL_REGEX.test(url)) { const path = self.location.pathname.split('/'); path.pop(); const basePath = path.join('/'); const baseUrl = self.location.protocol + '//' + basePath; // check if url has a leading dot if (url.charAt(0) === '.') { url = url.slice(1); } // check if url has a leading slash if (url.charAt(0) === '/') { url = url.slice(1); } return baseUrl + '/' + url; } const absoluteUrl = new URL(url, self.location.href); return absoluteUrl.href; } export function isBase64Image(src: string) { return src.startsWith('data:') === true; } export function calcFactoredRadius( radius: number, width: number, height: number, ): number { return radius * Math.min(Math.min(width, height) / (2.0 * radius), 1); } export function valuesAreEqual(values: number[]) { let prevValue = values[0]; for (let i = 1; i < values.length; i++) { if (prevValue !== values[i]) { return false; } } return true; } export function calcFactoredRadiusArray( radius: Vec4, width: number, height: number, ): [number, number, number, number] { const result: [number, number, number, number] = [ radius[0], radius[1], radius[2], radius[3], ]; const factor = Math.min( Math.min( Math.min( width / Math.max(width, radius[0] + radius[1]), width / Math.max(width, radius[2] + radius[3]), ), Math.min( height / Math.max(height, radius[0] + radius[3]), height / Math.max(height, radius[1] + radius[2]), ), ), 1, ); result[0] *= factor; result[1] *= factor; result[2] *= factor; result[3] *= factor; return result; }