CVBaseElement.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import assetManager from '../../utils/helpers/assetManager';
  2. import getBlendMode from '../../utils/helpers/blendModes';
  3. import Matrix from '../../3rd_party/transformation-matrix';
  4. import CVEffects from './CVEffects';
  5. import CVMaskElement from './CVMaskElement';
  6. import effectTypes from '../../utils/helpers/effectTypes';
  7. function CVBaseElement() {
  8. }
  9. var operationsMap = {
  10. 1: 'source-in',
  11. 2: 'source-out',
  12. 3: 'source-in',
  13. 4: 'source-out',
  14. };
  15. CVBaseElement.prototype = {
  16. createElements: function () {},
  17. initRendererElement: function () {},
  18. createContainerElements: function () {
  19. // If the layer is masked we will use two buffers to store each different states of the drawing
  20. // This solution is not ideal for several reason. But unfortunately, because of the recursive
  21. // nature of the render tree, it's the only simple way to make sure one inner mask doesn't override an outer mask.
  22. // TODO: try to reduce the size of these buffers to the size of the composition contaning the layer
  23. // It might be challenging because the layer most likely is transformed in some way
  24. if (this.data.tt >= 1) {
  25. this.buffers = [];
  26. var canvasContext = this.globalData.canvasContext;
  27. var bufferCanvas = assetManager.createCanvas(canvasContext.canvas.width, canvasContext.canvas.height);
  28. this.buffers.push(bufferCanvas);
  29. var bufferCanvas2 = assetManager.createCanvas(canvasContext.canvas.width, canvasContext.canvas.height);
  30. this.buffers.push(bufferCanvas2);
  31. if (this.data.tt >= 3 && !document._isProxy) {
  32. assetManager.loadLumaCanvas();
  33. }
  34. }
  35. this.canvasContext = this.globalData.canvasContext;
  36. this.transformCanvas = this.globalData.transformCanvas;
  37. this.renderableEffectsManager = new CVEffects(this);
  38. this.searchEffectTransforms();
  39. },
  40. createContent: function () {},
  41. setBlendMode: function () {
  42. var globalData = this.globalData;
  43. if (globalData.blendMode !== this.data.bm) {
  44. globalData.blendMode = this.data.bm;
  45. var blendModeValue = getBlendMode(this.data.bm);
  46. globalData.canvasContext.globalCompositeOperation = blendModeValue;
  47. }
  48. },
  49. createRenderableComponents: function () {
  50. this.maskManager = new CVMaskElement(this.data, this);
  51. this.transformEffects = this.renderableEffectsManager.getEffects(effectTypes.TRANSFORM_EFFECT);
  52. },
  53. hideElement: function () {
  54. if (!this.hidden && (!this.isInRange || this.isTransparent)) {
  55. this.hidden = true;
  56. }
  57. },
  58. showElement: function () {
  59. if (this.isInRange && !this.isTransparent) {
  60. this.hidden = false;
  61. this._isFirstFrame = true;
  62. this.maskManager._isFirstFrame = true;
  63. }
  64. },
  65. clearCanvas: function (canvasContext) {
  66. canvasContext.clearRect(
  67. this.transformCanvas.tx,
  68. this.transformCanvas.ty,
  69. this.transformCanvas.w * this.transformCanvas.sx,
  70. this.transformCanvas.h * this.transformCanvas.sy
  71. );
  72. },
  73. prepareLayer: function () {
  74. if (this.data.tt >= 1) {
  75. var buffer = this.buffers[0];
  76. var bufferCtx = buffer.getContext('2d');
  77. this.clearCanvas(bufferCtx);
  78. // on the first buffer we store the current state of the global drawing
  79. bufferCtx.drawImage(this.canvasContext.canvas, 0, 0);
  80. // The next four lines are to clear the canvas
  81. // TODO: Check if there is a way to clear the canvas without resetting the transform
  82. this.currentTransform = this.canvasContext.getTransform();
  83. this.canvasContext.setTransform(1, 0, 0, 1, 0, 0);
  84. this.clearCanvas(this.canvasContext);
  85. this.canvasContext.setTransform(this.currentTransform);
  86. }
  87. },
  88. exitLayer: function () {
  89. if (this.data.tt >= 1) {
  90. var buffer = this.buffers[1];
  91. // On the second buffer we store the current state of the global drawing
  92. // that only contains the content of this layer
  93. // (if it is a composition, it also includes the nested layers)
  94. var bufferCtx = buffer.getContext('2d');
  95. this.clearCanvas(bufferCtx);
  96. bufferCtx.drawImage(this.canvasContext.canvas, 0, 0);
  97. // We clear the canvas again
  98. this.canvasContext.setTransform(1, 0, 0, 1, 0, 0);
  99. this.clearCanvas(this.canvasContext);
  100. this.canvasContext.setTransform(this.currentTransform);
  101. // We draw the mask
  102. const mask = this.comp.getElementById('tp' in this.data ? this.data.tp : this.data.ind - 1);
  103. mask.renderFrame(true);
  104. // We draw the second buffer (that contains the content of this layer)
  105. this.canvasContext.setTransform(1, 0, 0, 1, 0, 0);
  106. // If the mask is a Luma matte, we need to do two extra painting operations
  107. // the _isProxy check is to avoid drawing a fake canvas in workers that will throw an error
  108. if (this.data.tt >= 3 && !document._isProxy) {
  109. // We copy the painted mask to a buffer that has a color matrix filter applied to it
  110. // that applies the rgb values to the alpha channel
  111. var lumaBuffer = assetManager.getLumaCanvas(this.canvasContext.canvas);
  112. var lumaBufferCtx = lumaBuffer.getContext('2d');
  113. lumaBufferCtx.drawImage(this.canvasContext.canvas, 0, 0);
  114. this.clearCanvas(this.canvasContext);
  115. // we repaint the context with the mask applied to it
  116. this.canvasContext.drawImage(lumaBuffer, 0, 0);
  117. }
  118. this.canvasContext.globalCompositeOperation = operationsMap[this.data.tt];
  119. this.canvasContext.drawImage(buffer, 0, 0);
  120. // We finally draw the first buffer (that contains the content of the global drawing)
  121. // We use destination-over to draw the global drawing below the current layer
  122. this.canvasContext.globalCompositeOperation = 'destination-over';
  123. this.canvasContext.drawImage(this.buffers[0], 0, 0);
  124. this.canvasContext.setTransform(this.currentTransform);
  125. // We reset the globalCompositeOperation to source-over, the standard type of operation
  126. this.canvasContext.globalCompositeOperation = 'source-over';
  127. }
  128. },
  129. renderFrame: function (forceRender) {
  130. if (this.hidden || this.data.hd) {
  131. return;
  132. }
  133. if (this.data.td === 1 && !forceRender) {
  134. return;
  135. }
  136. this.renderTransform();
  137. this.renderRenderable();
  138. this.renderLocalTransform();
  139. this.setBlendMode();
  140. var forceRealStack = this.data.ty === 0;
  141. this.prepareLayer();
  142. this.globalData.renderer.save(forceRealStack);
  143. this.globalData.renderer.ctxTransform(this.finalTransform.localMat.props);
  144. this.globalData.renderer.ctxOpacity(this.finalTransform.localOpacity);
  145. this.renderInnerContent();
  146. this.globalData.renderer.restore(forceRealStack);
  147. this.exitLayer();
  148. if (this.maskManager.hasMasks) {
  149. this.globalData.renderer.restore(true);
  150. }
  151. if (this._isFirstFrame) {
  152. this._isFirstFrame = false;
  153. }
  154. },
  155. destroy: function () {
  156. this.canvasContext = null;
  157. this.data = null;
  158. this.globalData = null;
  159. this.maskManager.destroy();
  160. },
  161. mHelper: new Matrix(),
  162. };
  163. CVBaseElement.prototype.hide = CVBaseElement.prototype.hideElement;
  164. CVBaseElement.prototype.show = CVBaseElement.prototype.showElement;
  165. export default CVBaseElement;