From efeb10970b6c665d1a72f94bc73f51c2299a88cb Mon Sep 17 00:00:00 2001 From: airnan Date: Fri, 26 May 2023 09:59:41 -0400 Subject: [PATCH] add support for transform effect --- player/js/3rd_party/transformation-matrix.js | 23 +++++++ player/js/effects/TransformEffect.js | 43 +++++++++++++ .../elements/canvasElements/CVBaseElement.js | 8 ++- .../js/elements/canvasElements/CVEffects.js | 48 +++++++++++++- .../effects/CVTransformEffect.js | 9 +++ .../elements/helpers/RenderableDOMElement.js | 1 + .../js/elements/helpers/TransformElement.js | 64 ++++++++++++++++++- .../helpers/shapes/SVGElementsRenderer.js | 4 +- .../helpers/shapes/ShapeTransformManager.js | 4 +- .../js/elements/svgElements/SVGBaseElement.js | 7 +- player/js/elements/svgElements/SVGEffects.js | 12 ++++ .../svgElements/effects/SVGTransformEffect.js | 10 +++ player/js/modules/canvas.js | 3 + player/js/modules/full.js | 5 ++ player/js/modules/html.js | 2 + player/js/modules/svg.js | 2 + player/js/renderers/CanvasRendererBase.js | 3 +- player/js/utils/helpers/effectTypes.js | 3 + 18 files changed, 233 insertions(+), 18 deletions(-) create mode 100644 player/js/effects/TransformEffect.js create mode 100644 player/js/elements/canvasElements/effects/CVTransformEffect.js create mode 100644 player/js/elements/svgElements/effects/SVGTransformEffect.js create mode 100644 player/js/utils/helpers/effectTypes.js diff --git a/player/js/3rd_party/transformation-matrix.js b/player/js/3rd_party/transformation-matrix.js index 560f5d709..c3a7dbdaa 100644 --- a/player/js/3rd_party/transformation-matrix.js +++ b/player/js/3rd_party/transformation-matrix.js @@ -209,6 +209,28 @@ const Matrix = (function () { return this; } + function multiply(matrix) { + var matrixProps = matrix.props; + return this.transform( + matrixProps[0], + matrixProps[1], + matrixProps[2], + matrixProps[3], + matrixProps[4], + matrixProps[5], + matrixProps[6], + matrixProps[7], + matrixProps[8], + matrixProps[9], + matrixProps[10], + matrixProps[11], + matrixProps[12], + matrixProps[13], + matrixProps[14], + matrixProps[15] + ); + } + function isIdentity() { if (!this._identityCalculated) { this._identity = !(this.props[0] !== 1 || this.props[1] !== 0 || this.props[2] !== 0 || this.props[3] !== 0 || this.props[4] !== 0 || this.props[5] !== 1 || this.props[6] !== 0 || this.props[7] !== 0 || this.props[8] !== 0 || this.props[9] !== 0 || this.props[10] !== 1 || this.props[11] !== 0 || this.props[12] !== 0 || this.props[13] !== 0 || this.props[14] !== 0 || this.props[15] !== 1); @@ -398,6 +420,7 @@ const Matrix = (function () { this.setTransform = setTransform; this.translate = translate; this.transform = transform; + this.multiply = multiply; this.applyToPoint = applyToPoint; this.applyToX = applyToX; this.applyToY = applyToY; diff --git a/player/js/effects/TransformEffect.js b/player/js/effects/TransformEffect.js new file mode 100644 index 000000000..d4bcc0dba --- /dev/null +++ b/player/js/effects/TransformEffect.js @@ -0,0 +1,43 @@ +import effectTypes from '../utils/helpers/effectTypes'; +import Matrix from '../3rd_party/transformation-matrix'; +import { degToRads } from '../utils/common'; + +function TransformEffect() { +} + +TransformEffect.prototype.init = function (effectsManager) { + this.effectsManager = effectsManager; + this.type = effectTypes.TRANSFORM_EFFECT; + this.matrix = new Matrix(); + this.opacity = -1; + this._mdf = false; + this._opMdf = false; +}; + +TransformEffect.prototype.renderFrame = function (forceFrame) { + this._opMdf = false; + this._mdf = false; + if (forceFrame || this.effectsManager._mdf) { + var effectElements = this.effectsManager.effectElements; + var anchor = effectElements[0].p.v; + var position = effectElements[1].p.v; + var scaleHeight = effectElements[3].p.v; + var scaleWidth = effectElements[4].p.v; + var skew = effectElements[5].p.v; + var skewAxis = effectElements[6].p.v; + var rotation = effectElements[7].p.v; + this.matrix.reset(); + this.matrix.translate(-anchor[0], -anchor[1], anchor[2]); + this.matrix.scale(scaleWidth * 0.01, scaleHeight * 0.01, 1); + this.matrix.rotate(-rotation * degToRads); + this.matrix.skewFromAxis(-skew * degToRads, (skewAxis + 90) * degToRads); + this.matrix.translate(position[0], position[1], 0); + this._mdf = true; + if (this.opacity !== effectElements[8].p.v) { + this.opacity = effectElements[8].p.v; + this._opMdf = true; + } + } +}; + +export default TransformEffect; diff --git a/player/js/elements/canvasElements/CVBaseElement.js b/player/js/elements/canvasElements/CVBaseElement.js index dc1e649f6..b93734e63 100644 --- a/player/js/elements/canvasElements/CVBaseElement.js +++ b/player/js/elements/canvasElements/CVBaseElement.js @@ -3,6 +3,7 @@ import getBlendMode from '../../utils/helpers/blendModes'; import Matrix from '../../3rd_party/transformation-matrix'; import CVEffects from './CVEffects'; import CVMaskElement from './CVMaskElement'; +import effectTypes from '../../utils/helpers/effectTypes'; function CVBaseElement() { } @@ -37,6 +38,7 @@ CVBaseElement.prototype = { this.canvasContext = this.globalData.canvasContext; this.transformCanvas = this.globalData.transformCanvas; this.renderableEffectsManager = new CVEffects(this); + this.searchEffectTransforms(); }, createContent: function () {}, setBlendMode: function () { @@ -49,6 +51,7 @@ CVBaseElement.prototype = { }, createRenderableComponents: function () { this.maskManager = new CVMaskElement(this.data, this); + this.transformEffects = this.renderableEffectsManager.getEffects(effectTypes.TRANSFORM_EFFECT); }, hideElement: function () { if (!this.hidden && (!this.isInRange || this.isTransparent)) { @@ -136,12 +139,13 @@ CVBaseElement.prototype = { } this.renderTransform(); this.renderRenderable(); + this.renderLocalTransform(); this.setBlendMode(); var forceRealStack = this.data.ty === 0; this.prepareLayer(); this.globalData.renderer.save(forceRealStack); - this.globalData.renderer.ctxTransform(this.finalTransform.mat.props); - this.globalData.renderer.ctxOpacity(this.finalTransform.mProp.o.v); + this.globalData.renderer.ctxTransform(this.finalTransform.localMat.props); + this.globalData.renderer.ctxOpacity(this.finalTransform.localOpacity); this.renderInnerContent(); this.globalData.renderer.restore(forceRealStack); this.exitLayer(); diff --git a/player/js/elements/canvasElements/CVEffects.js b/player/js/elements/canvasElements/CVEffects.js index 494e8af90..c5f7ed154 100644 --- a/player/js/elements/canvasElements/CVEffects.js +++ b/player/js/elements/canvasElements/CVEffects.js @@ -1,6 +1,50 @@ -function CVEffects() { +var registeredEffects = {}; +function CVEffects(elem) { + var i; + var len = elem.data.ef ? elem.data.ef.length : 0; + this.filters = []; + var filterManager; + for (i = 0; i < len; i += 1) { + filterManager = null; + var type = elem.data.ef[i].ty; + if (registeredEffects[type]) { + var Effect = registeredEffects[type].effect; + filterManager = new Effect(elem.effectsManager.effectElements[i], elem); + } + if (filterManager) { + this.filters.push(filterManager); + } + } + if (this.filters.length) { + elem.addRenderableComponent(this); + } +} + +CVEffects.prototype.renderFrame = function (_isFirstFrame) { + var i; + var len = this.filters.length; + for (i = 0; i < len; i += 1) { + this.filters[i].renderFrame(_isFirstFrame); + } +}; + +CVEffects.prototype.getEffects = function (type) { + var i; + var len = this.filters.length; + var effects = []; + for (i = 0; i < len; i += 1) { + if (this.filters[i].type === type) { + effects.push(this.filters[i]); + } + } + return effects; +}; + +export function registerEffect(id, effect) { + registeredEffects[id] = { + effect, + }; } -CVEffects.prototype.renderFrame = function () {}; export default CVEffects; diff --git a/player/js/elements/canvasElements/effects/CVTransformEffect.js b/player/js/elements/canvasElements/effects/CVTransformEffect.js new file mode 100644 index 000000000..190d4dd23 --- /dev/null +++ b/player/js/elements/canvasElements/effects/CVTransformEffect.js @@ -0,0 +1,9 @@ +import TransformEffect from '../../../effects/TransformEffect'; +import { extendPrototype } from '../../../utils/functionExtensions'; + +function CVTransformEffect(effectsManager) { + this.init(effectsManager); +} +extendPrototype([TransformEffect], CVTransformEffect); + +export default CVTransformEffect; diff --git a/player/js/elements/helpers/RenderableDOMElement.js b/player/js/elements/helpers/RenderableDOMElement.js index efc201b59..8acd2f920 100644 --- a/player/js/elements/helpers/RenderableDOMElement.js +++ b/player/js/elements/helpers/RenderableDOMElement.js @@ -47,6 +47,7 @@ function RenderableDOMElement() {} } this.renderTransform(); this.renderRenderable(); + this.renderLocalTransform(); this.renderElement(); this.renderInnerContent(); if (this._isFirstFrame) { diff --git a/player/js/elements/helpers/TransformElement.js b/player/js/elements/helpers/TransformElement.js index c0050ca4c..cbb2391fd 100644 --- a/player/js/elements/helpers/TransformElement.js +++ b/player/js/elements/helpers/TransformElement.js @@ -1,15 +1,20 @@ import Matrix from '../../3rd_party/transformation-matrix'; import TransformPropertyFactory from '../../utils/TransformProperty'; +import effectTypes from '../../utils/helpers/effectTypes'; function TransformElement() {} TransformElement.prototype = { initTransform: function () { + var mat = new Matrix(); this.finalTransform = { mProp: this.data.ks ? TransformPropertyFactory.getTransformProperty(this, this.data.ks, this) : { o: 0 }, _matMdf: false, + _localMatMdf: false, _opMdf: false, - mat: new Matrix(), + mat: mat, + localMat: mat, + localOpacity: 1, }; if (this.data.ao) { this.finalTransform.mProp.autoOriented = true; @@ -44,8 +49,61 @@ TransformElement.prototype = { mat = this.finalTransform.mProp.v.props; finalMat.cloneFromProps(mat); for (i = 0; i < len; i += 1) { - mat = this.hierarchy[i].finalTransform.mProp.v.props; - finalMat.transform(mat[0], mat[1], mat[2], mat[3], mat[4], mat[5], mat[6], mat[7], mat[8], mat[9], mat[10], mat[11], mat[12], mat[13], mat[14], mat[15]); + finalMat.multiply(this.hierarchy[i].finalTransform.mProp.v); + } + } + } + if (this.finalTransform._matMdf) { + this.finalTransform._localMatMdf = this.finalTransform._matMdf; + } + if (this.finalTransform._opMdf) { + this.finalTransform.localOpacity = this.finalTransform.mProp.o.v; + } + }, + renderLocalTransform: function () { + if (this.localTransforms) { + var i = 0; + var len = this.localTransforms.length; + this.finalTransform._localMatMdf = this.finalTransform._matMdf; + if (!this.finalTransform._localMatMdf || !this.finalTransform._opMdf) { + while (i < len) { + if (this.localTransforms[i]._mdf) { + this.finalTransform._localMatMdf = true; + } + if (this.localTransforms[i]._opMdf) { + this.finalTransform._opMdf = true; + } + i += 1; + } + } + if (this.finalTransform._localMatMdf) { + var localMat = this.finalTransform.localMat; + this.localTransforms[0].matrix.clone(localMat); + for (i = 1; i < len; i += 1) { + var lmat = this.localTransforms[i].matrix; + localMat.multiply(lmat); + } + localMat.multiply(this.finalTransform.mat); + } + if (this.finalTransform._opMdf) { + var localOp = this.finalTransform.localOpacity; + for (i = 0; i < len; i += 1) { + localOp *= this.localTransforms[i].opacity * 0.01; + } + this.finalTransform.localOpacity = localOp; + } + } + }, + searchEffectTransforms: function () { + if (this.renderableEffectsManager) { + var transformEffects = this.renderableEffectsManager.getEffects(effectTypes.TRANSFORM_EFFECT); + if (transformEffects.length) { + this.localTransforms = []; + this.finalTransform.localMat = new Matrix(); + var i = 0; + var len = transformEffects.length; + for (i = 0; i < len; i += 1) { + this.localTransforms.push(transformEffects[i]); } } } diff --git a/player/js/elements/helpers/shapes/SVGElementsRenderer.js b/player/js/elements/helpers/shapes/SVGElementsRenderer.js index 5638f558d..2112f2cf8 100644 --- a/player/js/elements/helpers/shapes/SVGElementsRenderer.js +++ b/player/js/elements/helpers/shapes/SVGElementsRenderer.js @@ -58,7 +58,6 @@ const SVGElementsRenderer = (function () { var lvl = itemData.lvl; var paths; var mat; - var props; var iterations; var k; for (l = 0; l < lLen; l += 1) { @@ -76,8 +75,7 @@ const SVGElementsRenderer = (function () { iterations = lvl - itemData.styles[l].lvl; k = itemData.transformers.length - 1; while (iterations > 0) { - props = itemData.transformers[k].mProps.v.props; - mat.transform(props[0], props[1], props[2], props[3], props[4], props[5], props[6], props[7], props[8], props[9], props[10], props[11], props[12], props[13], props[14], props[15]); + mat.multiply(itemData.transformers[k].mProps.v); iterations -= 1; k -= 1; } diff --git a/player/js/elements/helpers/shapes/ShapeTransformManager.js b/player/js/elements/helpers/shapes/ShapeTransformManager.js index 421eea427..13c095dc1 100644 --- a/player/js/elements/helpers/shapes/ShapeTransformManager.js +++ b/player/js/elements/helpers/shapes/ShapeTransformManager.js @@ -38,11 +38,9 @@ ShapeTransformManager.prototype = { i += 1; } if (_mdf) { - var props; sequence.finalTransform.reset(); for (i = len - 1; i >= 0; i -= 1) { - props = sequence.transforms[i].transform.mProps.v.props; - sequence.finalTransform.transform(props[0], props[1], props[2], props[3], props[4], props[5], props[6], props[7], props[8], props[9], props[10], props[11], props[12], props[13], props[14], props[15]); + sequence.finalTransform.multiply(sequence.transforms[i].transform.mProps.v); } } sequence._mdf = _mdf; diff --git a/player/js/elements/svgElements/SVGBaseElement.js b/player/js/elements/svgElements/SVGBaseElement.js index dfda964de..5b702edd6 100644 --- a/player/js/elements/svgElements/SVGBaseElement.js +++ b/player/js/elements/svgElements/SVGBaseElement.js @@ -71,11 +71,11 @@ SVGBaseElement.prototype = { } }, renderElement: function () { - if (this.finalTransform._matMdf) { - this.transformedElement.setAttribute('transform', this.finalTransform.mat.to2dCSS()); + if (this.finalTransform._localMatMdf) { + this.transformedElement.setAttribute('transform', this.finalTransform.localMat.to2dCSS()); } if (this.finalTransform._opMdf) { - this.transformedElement.setAttribute('opacity', this.finalTransform.mProp.o.v); + this.transformedElement.setAttribute('opacity', this.finalTransform.localOpacity); } }, destroyBaseElement: function () { @@ -92,6 +92,7 @@ SVGBaseElement.prototype = { createRenderableComponents: function () { this.maskManager = new MaskElement(this.data, this, this.globalData); this.renderableEffectsManager = new SVGEffects(this); + this.searchEffectTransforms(); }, getMatte: function (matteType) { // This should not be a common case. But for backward compatibility, we'll create the matte object. diff --git a/player/js/elements/svgElements/SVGEffects.js b/player/js/elements/svgElements/SVGEffects.js index ef22f3a9a..adf84a4a1 100644 --- a/player/js/elements/svgElements/SVGEffects.js +++ b/player/js/elements/svgElements/SVGEffects.js @@ -48,6 +48,18 @@ SVGEffects.prototype.renderFrame = function (_isFirstFrame) { } }; +SVGEffects.prototype.getEffects = function (type) { + var i; + var len = this.filters.length; + var effects = []; + for (i = 0; i < len; i += 1) { + if (this.filters[i].type === type) { + effects.push(this.filters[i]); + } + } + return effects; +}; + export function registerEffect(id, effect, countsAsEffect) { registeredEffects[id] = { effect, diff --git a/player/js/elements/svgElements/effects/SVGTransformEffect.js b/player/js/elements/svgElements/effects/SVGTransformEffect.js new file mode 100644 index 000000000..315207dda --- /dev/null +++ b/player/js/elements/svgElements/effects/SVGTransformEffect.js @@ -0,0 +1,10 @@ +import TransformEffect from '../../../effects/TransformEffect'; +import { extendPrototype } from '../../../utils/functionExtensions'; + +function SVGTransformEffect(_, filterManager) { + this.init(filterManager); +} + +extendPrototype([TransformEffect], SVGTransformEffect); + +export default SVGTransformEffect; diff --git a/player/js/modules/canvas.js b/player/js/modules/canvas.js index baa8010b1..6b774a694 100644 --- a/player/js/modules/canvas.js +++ b/player/js/modules/canvas.js @@ -7,11 +7,14 @@ import Expressions from '../utils/expressions/Expressions'; import interfacesProvider from '../utils/expressions/InterfacesProvider'; import expressionPropertyDecorator from '../utils/expressions/ExpressionPropertyDecorator'; import expressionTextPropertyDecorator from '../utils/expressions/ExpressionTextPropertyDecorator'; +import CVTransformEffect from '../elements/canvasElements/effects/CVTransformEffect'; +import { registerEffect } from '../elements/canvasElements/CVEffects'; // Registering expression plugin setExpressionsPlugin(Expressions); setExpressionInterfaces(interfacesProvider); expressionPropertyDecorator(); expressionTextPropertyDecorator(); +registerEffect(35, CVTransformEffect); export default lottie; diff --git a/player/js/modules/full.js b/player/js/modules/full.js index 9ce6b085e..e449305a7 100644 --- a/player/js/modules/full.js +++ b/player/js/modules/full.js @@ -30,6 +30,9 @@ import SVGProLevelsFilter from '../elements/svgElements/effects/SVGProLevelsFilt import SVGDropShadowEffect from '../elements/svgElements/effects/SVGDropShadowEffect'; import SVGMatte3Effect from '../elements/svgElements/effects/SVGMatte3Effect'; import SVGGaussianBlurEffect from '../elements/svgElements/effects/SVGGaussianBlurEffect'; +import SVGTransformEffect from '../elements/svgElements/effects/SVGTransformEffect'; +import CVTransformEffect from '../elements/canvasElements/effects/CVTransformEffect'; +import { registerEffect as canvasRegisterEffect } from '../elements/canvasElements/CVEffects'; // Registering renderers registerRenderer('canvas', CanvasRenderer); @@ -59,5 +62,7 @@ registerEffect(24, SVGProLevelsFilter, true); registerEffect(25, SVGDropShadowEffect, true); registerEffect(28, SVGMatte3Effect, false); registerEffect(29, SVGGaussianBlurEffect, true); +registerEffect(35, SVGTransformEffect, false); +canvasRegisterEffect(35, CVTransformEffect); export default lottie; diff --git a/player/js/modules/html.js b/player/js/modules/html.js index 3e6518639..a47fc02ee 100644 --- a/player/js/modules/html.js +++ b/player/js/modules/html.js @@ -17,6 +17,7 @@ import SVGProLevelsFilter from '../elements/svgElements/effects/SVGProLevelsFilt import SVGDropShadowEffect from '../elements/svgElements/effects/SVGDropShadowEffect'; import SVGMatte3Effect from '../elements/svgElements/effects/SVGMatte3Effect'; import SVGGaussianBlurEffect from '../elements/svgElements/effects/SVGGaussianBlurEffect'; +import SVGTransformEffect from '../elements/svgElements/effects/SVGTransformEffect'; // Registering expression plugin setExpressionsPlugin(Expressions); @@ -31,5 +32,6 @@ registerEffect(24, SVGProLevelsFilter, true); registerEffect(25, SVGDropShadowEffect, true); registerEffect(28, SVGMatte3Effect, false); registerEffect(29, SVGGaussianBlurEffect, true); +registerEffect(35, SVGTransformEffect, false); export default lottie; diff --git a/player/js/modules/svg.js b/player/js/modules/svg.js index 4c7f5dfb4..7df61ad4f 100644 --- a/player/js/modules/svg.js +++ b/player/js/modules/svg.js @@ -17,6 +17,7 @@ import SVGProLevelsFilter from '../elements/svgElements/effects/SVGProLevelsFilt import SVGDropShadowEffect from '../elements/svgElements/effects/SVGDropShadowEffect'; import SVGMatte3Effect from '../elements/svgElements/effects/SVGMatte3Effect'; import SVGGaussianBlurEffect from '../elements/svgElements/effects/SVGGaussianBlurEffect'; +import SVGTransformEffect from '../elements/svgElements/effects/SVGTransformEffect'; // Registering expression plugin setExpressionsPlugin(Expressions); @@ -31,5 +32,6 @@ registerEffect(24, SVGProLevelsFilter, true); registerEffect(25, SVGDropShadowEffect, true); registerEffect(28, SVGMatte3Effect, false); registerEffect(29, SVGGaussianBlurEffect, true); +registerEffect(35, SVGTransformEffect, false); export default lottie; diff --git a/player/js/renderers/CanvasRendererBase.js b/player/js/renderers/CanvasRendererBase.js index 1f2868201..6c801ece6 100644 --- a/player/js/renderers/CanvasRendererBase.js +++ b/player/js/renderers/CanvasRendererBase.js @@ -76,9 +76,8 @@ CanvasRendererBase.prototype.ctxTransform = function (props) { this.transformMat.cloneFromProps(props); // Taking the last transform value from the stored stack of transforms var currentTransform = this.contextData.getTransform(); - var cProps = currentTransform.props; // Applying the last transform value after the new transform to respect the order of transformations - this.transformMat.transform(cProps[0], cProps[1], cProps[2], cProps[3], cProps[4], cProps[5], cProps[6], cProps[7], cProps[8], cProps[9], cProps[10], cProps[11], cProps[12], cProps[13], cProps[14], cProps[15]); + this.transformMat.multiply(currentTransform); // Storing the new transformed value in the stored transform currentTransform.cloneFromProps(this.transformMat.props); var trProps = currentTransform.props; diff --git a/player/js/utils/helpers/effectTypes.js b/player/js/utils/helpers/effectTypes.js new file mode 100644 index 000000000..ca9453f32 --- /dev/null +++ b/player/js/utils/helpers/effectTypes.js @@ -0,0 +1,3 @@ +export default { + TRANSFORM_EFFECT: 'transformEFfect', +};