UNPKG

pixi.js

Version:

<p align="center"> <a href="https://pixijs.com" target="_blank" rel="noopener noreferrer"> <img height="150" src="https://files.pixijs.download/branding/pixijs-logo-transparent-dark.svg?v=1" alt="PixiJS logo"> </a> </p> <br/> <p align="center">

261 lines (257 loc) 8.68 kB
'use strict'; var Extensions = require('../../../extensions/Extensions.js'); var Bounds = require('../../../scene/container/bounds/Bounds.js'); var getRenderableBounds = require('../../../scene/container/bounds/getRenderableBounds.js'); var getPo2TextureFromSource = require('../../../scene/text/utils/getPo2TextureFromSource.js'); var CanvasPool = require('../shared/texture/CanvasPool.js'); var canvasUtils = require('./utils/canvasUtils.js'); "use strict"; function isCanvasFilterCapable(filter) { return typeof filter.getCanvasFilterString === "function"; } class CanvasFilterFrame { constructor() { this.skip = false; this.useClip = false; this.filters = null; this.container = null; this.bounds = new Bounds.Bounds(); this.cssFilterString = ""; } } class CanvasFilterSystem { /** * @param renderer - The Canvas renderer * @param renderer.canvasContext * @param renderer.canvasContext.activeContext * @param renderer.canvasContext.activeResolution */ constructor(renderer) { this._filterStack = []; this._filterStackIndex = 0; this._savedStates = []; this._alphaMultiplier = 1; this._warnedFilterTypes = /* @__PURE__ */ new Set(); this.renderer = renderer; } /** * Push a filter instruction onto the stack. * Called when entering a filtered container. * @param instruction - The filter instruction from FilterPipe */ push(instruction) { const filterFrame = this._pushFilterFrame(); const filters = instruction.filterEffect.filters; filterFrame.skip = false; filterFrame.useClip = false; filterFrame.filters = filters; filterFrame.container = instruction.container; filterFrame.cssFilterString = ""; if (filters.every((filter) => !filter.enabled)) { filterFrame.skip = true; return; } const cssFilters = []; const alphaMultiplier = 1; for (const filter of filters) { if (!filter.enabled) continue; if (!isCanvasFilterCapable(filter)) { this._warnUnsupportedFilter(filter); continue; } const cssString = filter.getCanvasFilterString(); if (cssString === null) { this._warnUnsupportedFilter(filter); continue; } if (cssString) { cssFilters.push(cssString); } } if (cssFilters.length === 0 && alphaMultiplier === 1) { filterFrame.skip = true; return; } filterFrame.cssFilterString = cssFilters.join(" "); this._calculateFilterArea(instruction, filterFrame.bounds); filterFrame.useClip = !!instruction.filterEffect.filterArea; const context = this.renderer.canvasContext.activeContext; const previousFilter = context.filter || "none"; this._savedStates.push({ filter: previousFilter, alphaMultiplier: this._alphaMultiplier }); if (filterFrame.useClip && Number.isFinite(filterFrame.bounds.width) && Number.isFinite(filterFrame.bounds.height) && filterFrame.bounds.width > 0 && filterFrame.bounds.height > 0) { const resolution = this.renderer.canvasContext.activeResolution || 1; context.save(); context.setTransform(1, 0, 0, 1, 0, 0); context.beginPath(); context.rect( filterFrame.bounds.x * resolution, filterFrame.bounds.y * resolution, filterFrame.bounds.width * resolution, filterFrame.bounds.height * resolution ); context.clip(); } else { filterFrame.useClip = false; } if (alphaMultiplier !== 1) { this._alphaMultiplier *= alphaMultiplier; } if (filterFrame.cssFilterString) { context.filter = previousFilter !== "none" ? `${previousFilter} ${filterFrame.cssFilterString}` : filterFrame.cssFilterString; } } /** Pop a filter from the stack. Called when exiting a filtered container. */ pop() { const filterFrame = this._popFilterFrame(); if (filterFrame.skip) { return; } const savedState = this._savedStates.pop(); if (!savedState) { return; } const context = this.renderer.canvasContext.activeContext; if (filterFrame.useClip) { context.restore(); } else { context.filter = savedState.filter; } this._alphaMultiplier = savedState.alphaMultiplier; } /** * Applies supported filters to a texture and returns a new texture. * Unsupported filters are skipped with a warn-once message. * @param params - The parameters for applying filters. * @param params.texture * @param params.filters * @returns The resulting texture after filters are applied. */ generateFilteredTexture({ texture, filters }) { if (!filters?.length || filters.every((filter) => !filter.enabled)) { return texture; } const cssFilters = []; const alphaMultiplier = 1; for (const filter of filters) { if (!filter.enabled) continue; if (!isCanvasFilterCapable(filter)) { this._warnUnsupportedFilter(filter); continue; } const cssString = filter.getCanvasFilterString(); if (cssString === null) { this._warnUnsupportedFilter(filter); continue; } if (cssString) { cssFilters.push(cssString); } } if (cssFilters.length === 0 && alphaMultiplier === 1) { return texture; } const source = canvasUtils.canvasUtils.getCanvasSource(texture); if (!source) { return texture; } const frame = texture.frame; const resolution = texture.source._resolution ?? texture.source.resolution ?? 1; const width = frame.width; const height = frame.height; const canvasAndContext = CanvasPool.CanvasPool.getOptimalCanvasAndContext(width, height, resolution); const { canvas, context } = canvasAndContext; context.setTransform(1, 0, 0, 1, 0, 0); context.clearRect(0, 0, canvas.width, canvas.height); if (cssFilters.length) { context.filter = cssFilters.join(" "); } if (alphaMultiplier !== 1) { context.globalAlpha = alphaMultiplier; } const sx = frame.x * resolution; const sy = frame.y * resolution; const sw = width * resolution; const sh = height * resolution; context.drawImage( source, sx, sy, sw, sh, 0, 0, sw, sh ); context.filter = "none"; context.globalAlpha = 1; return getPo2TextureFromSource.getPo2TextureFromSource(canvas, width, height, resolution); } /** * Calculate the filter area bounds. * @param instruction - Filter instruction * @param bounds - Bounds object to populate */ _calculateFilterArea(instruction, bounds) { if (instruction.renderables) { getRenderableBounds.getGlobalRenderableBounds(instruction.renderables, bounds); } else if (instruction.filterEffect.filterArea) { bounds.clear(); bounds.addRect(instruction.filterEffect.filterArea); bounds.applyMatrix(instruction.container.worldTransform); } else { instruction.container.getFastGlobalBounds(true, bounds); } if (instruction.container) { const renderGroup = instruction.container.renderGroup || instruction.container.parentRenderGroup; const filterFrameTransform = renderGroup?.cacheToLocalTransform; if (filterFrameTransform) { bounds.applyMatrix(filterFrameTransform); } } } _warnUnsupportedFilter(filter) { const filterName = filter?.constructor?.name || "Filter"; if (this._warnedFilterTypes.has(filterName)) { return; } this._warnedFilterTypes.add(filterName); console.warn( `CanvasRenderer: filter "${filterName}" is not supported in Canvas2D and will be skipped.` ); } get alphaMultiplier() { return this._alphaMultiplier; } _pushFilterFrame() { let filterFrame = this._filterStack[this._filterStackIndex]; if (!filterFrame) { filterFrame = this._filterStack[this._filterStackIndex] = new CanvasFilterFrame(); } this._filterStackIndex++; return filterFrame; } _popFilterFrame() { if (this._filterStackIndex <= 0) { return this._filterStack[0]; } this._filterStackIndex--; return this._filterStack[this._filterStackIndex]; } /** Destroys the system */ destroy() { this._filterStack = null; this._savedStates = null; this._warnedFilterTypes = null; this._alphaMultiplier = 1; } } /** @ignore */ CanvasFilterSystem.extension = { type: [Extensions.ExtensionType.CanvasSystem], name: "filter" }; Extensions.extensions.add(CanvasFilterSystem); exports.CanvasFilterSystem = CanvasFilterSystem; exports.isCanvasFilterCapable = isCanvasFilterCapable; //# sourceMappingURL=CanvasFilterSystem.js.map