Source: tiny/core/renderers/canvas/CanvasRenderer.js

import SystemRenderer from '../SystemRenderer';
import CanvasMaskManager from './utils/CanvasMaskManager';
import CanvasRenderTarget from './utils/CanvasRenderTarget';
import mapCanvasBlendModesToTiny from './utils/mapCanvasBlendModesToTiny';
import { pluginTarget } from '../../utils';
import { RENDERER_TYPE, SCALE_MODES, BLEND_MODES } from '../../const';
import settings, { config } from '../../settings';

/**
 * The CanvasRenderer draws the scene and all its content onto a 2d canvas. This renderer should be used for browsers that do not support WebGL. Don't forget to add the CanvasRenderer.view to your DOM or you will not see anything :)
 *
 * @class
 * @memberof Tiny
 * @extends Tiny.SystemRenderer
 */
export default class CanvasRenderer extends SystemRenderer {
  /**
   * @param {number} width - the width of the screen
   * @param {number} height - the height of the screen
   * @param {object} [options] - The optional renderer parameters
   * @param {HTMLCanvasElement} [options.view] - the canvas to use as a view, optional
   * @param {boolean} [options.antialias=false] - sets antialias (only applicable in chrome at the moment)
   * @param {boolean} [options.autoResize=false] - If the render view is automatically resized, default false
   * @param {number} [options.backgroundColor=0x000000] - The background color of the rendered area (shown if not transparent).
   * @param {boolean} [options.clearBeforeRender=true] - This sets if the CanvasRenderer will clear the canvas or not before the new render pass.
   * @param {number} [options.resolution=1] - The resolution / device pixel ratio of the renderer. The resolution of the renderer retina would be 2.
   * @param {boolean} [options.roundPixels=false] - If true Tiny will Math.floor() x/y values when rendering, stopping pixel interpolation.
   * @param {boolean} [options.transparent=false] - If the render view is transparent, default false
   */
  constructor(width, height, options) {
    super('Canvas', width, height, options);

    this.type = RENDERER_TYPE.CANVAS;

    /**
     * @version 1.1.9
     * @member {object}
     * @private
     */
    this._contextOptions = Object.assign({ alpha: this.transparent }, config.extraContextAttributes || {});

    /**
     * The canvas 2d context that everything is drawn with.
     *
     * @member {CanvasRenderingContext2D}
     */
    this.rootContext = this.view.getContext('2d', this._contextOptions);

    /**
     * The currently active canvas 2d context (could change with renderTextures)
     *
     * @version 1.2.0
     * @member {CanvasRenderingContext2D}
     */
    this.context = this.rootContext;

    /**
     * Boolean flag controlling canvas refresh.
     *
     * @member {boolean}
     */
    this.refresh = true;

    /**
     * Instance of a CanvasMaskManager, handles masking when using the canvas renderer.
     *
     * @member {Tiny.CanvasMaskManager}
     */
    this.maskManager = new CanvasMaskManager(this);

    /**
     * The canvas property used to set the canvas smoothing property.
     *
     * @member {string}
     */
    this.smoothProperty = 'imageSmoothingEnabled';

    if (!this.rootContext.imageSmoothingEnabled) {
      if (this.rootContext.webkitImageSmoothingEnabled) {
        this.smoothProperty = 'webkitImageSmoothingEnabled';
      } else if (this.rootContext.mozImageSmoothingEnabled) {
        this.smoothProperty = 'mozImageSmoothingEnabled';
      } else if (this.rootContext.oImageSmoothingEnabled) {
        this.smoothProperty = 'oImageSmoothingEnabled';
      } else if (this.rootContext.msImageSmoothingEnabled) {
        this.smoothProperty = 'msImageSmoothingEnabled';
      }
    }

    this.initPlugins();

    this.blendModes = mapCanvasBlendModesToTiny();
    this._activeBlendMode = null;

    this.renderingToScreen = false;

    this.resize(this.options.width, this.options.height);

    /**
     * Fired after rendering finishes.
     *
     * @event Tiny.CanvasRenderer#postrender
     */

    /**
     * Fired before rendering starts.
     *
     * @event Tiny.CanvasRenderer#prerender
     */
  }

  /**
   * Renders the object to this canvas view
   *
   * @param {Tiny.DisplayObject} displayObject - The object to be rendered
   * @param {Tiny.RenderTexture} [renderTexture] - A render texture to be rendered to. If unset, it will render to the root context.
   * @param {boolean} [clear=false] - Whether to clear the canvas before drawing
   * @param {Tiny.Transform} [transform] - A transformation to be applied
   * @param {boolean} [skipUpdateTransform=false] - Whether to skip the update transform
   */
  render(displayObject, renderTexture, clear, transform, skipUpdateTransform) {
    if (!this.view) {
      return;
    }

    // can be handy to know!
    this.renderingToScreen = !renderTexture;

    this.emit('prerender');

    const rootResolution = this.resolution;

    if (renderTexture) {
      renderTexture = renderTexture.baseTexture || renderTexture;

      if (!renderTexture._canvasRenderTarget) {
        renderTexture._canvasRenderTarget = new CanvasRenderTarget(
          renderTexture.width,
          renderTexture.height,
          renderTexture.resolution
        );
        renderTexture.source = renderTexture._canvasRenderTarget.canvas;
        renderTexture.valid = true;
      }

      this.context = renderTexture._canvasRenderTarget.context;
      this.resolution = renderTexture._canvasRenderTarget.resolution;
    } else {
      this.context = this.rootContext;
    }

    const context = this.context;

    if (!renderTexture) {
      this._lastObjectRendered = displayObject;
    }

    if (!skipUpdateTransform) {
      // update the scene graph
      const cacheParent = displayObject.parent;
      const tempWt = this._tempDisplayObjectParent.transform.worldTransform;

      if (transform) {
        transform.copy(tempWt);

        // lets not forget to flag the parent transform as dirty...
        this._tempDisplayObjectParent.transform._worldID = -1;
      } else {
        tempWt.identity();
      }

      displayObject.parent = this._tempDisplayObjectParent;
      displayObject.updateTransform();
      displayObject.parent = cacheParent;
      // displayObject.hitArea = //TODO add a temp hit area
    }

    context.save();
    context.setTransform(1, 0, 0, 1, 0, 0);
    context.globalAlpha = 1;
    this._activeBlendMode = BLEND_MODES.NORMAL;
    context.globalCompositeOperation = this.blendModes[BLEND_MODES.NORMAL];

    if (clear !== undefined ? clear : this.clearBeforeRender) {
      if (this.renderingToScreen) {
        if (this.transparent) {
          context.clearRect(0, 0, this.width, this.height);
        } else {
          context.fillStyle = this._backgroundColorString;
          context.fillRect(0, 0, this.width, this.height);
        }
      } // else {
      // TODO: implement background for CanvasRenderTarget or RenderTexture?
      // }
    }

    // TODO RENDER TARGET STUFF HERE..
    const tempContext = this.context;

    this.context = context;
    displayObject.renderCanvas(this);
    this.context = tempContext;

    context.restore();

    this.resolution = rootResolution;

    this.emit('postrender');
  }

  /**
   * Clear the canvas of renderer.
   *
   * @param {string} [clearColor] - Clear the canvas with this color, except the canvas is transparent.
   */
  clear(clearColor) {
    const context = this.context;

    clearColor = clearColor || this._backgroundColorString;

    if (!this.transparent && clearColor) {
      context.fillStyle = clearColor;
      context.fillRect(0, 0, this.width, this.height);
    } else {
      context.clearRect(0, 0, this.width, this.height);
    }
  }

  /**
   * Sets the blend mode of the renderer.
   *
   * @param {number} blendMode - See {@link Tiny.BLEND_MODES} for valid values.
   */
  setBlendMode(blendMode) {
    if (this._activeBlendMode === blendMode) {
      return;
    }

    this._activeBlendMode = blendMode;
    this.context.globalCompositeOperation = this.blendModes[blendMode];
  }

  /**
   * Removes everything from the renderer and optionally removes the Canvas DOM element.
   *
   * @param {boolean} [removeView=false] - Removes the Canvas element from the DOM.
   */
  destroy(removeView) {
    this.destroyPlugins();

    // call the base destroy
    super.destroy(removeView);

    this.context = null;

    this.refresh = true;

    this.maskManager.destroy();
    this.maskManager = null;

    this.smoothProperty = null;
  }

  /**
   * Resizes the canvas view to the specified width and height.
   *
   * @extends Tiny.SystemRenderer#resize
   *
   * @param {number} screenWidth - the new width of the screen
   * @param {number} screenHeight - the new height of the screen
   */
  resize(screenWidth, screenHeight) {
    super.resize(screenWidth, screenHeight);

    // reset the scale mode.. oddly this seems to be reset when the canvas is resized.
    // surely a browser bug?? Let TinyJS fix that for you..
    if (this.smoothProperty) {
      this.rootContext[this.smoothProperty] = (settings.SCALE_MODE === SCALE_MODES.LINEAR);
    }
  }

  /**
   * Checks if blend mode has changed.
   *
   * @version 1.2.0
   */
  invalidateBlendMode() {
    this._activeBlendMode = this.blendModes.indexOf(this.context.globalCompositeOperation);
  }
}

/**
 * Collection of installed plugins. These are included by default in TinyJS, but can be excluded by creating a custom build. Consult the README for more information about creating custom builds and excluding plugins.
 *
 * @name Tiny.CanvasRenderer#plugins
 * @type {object}
 * @readonly
 * @property {Tiny.accessibility.AccessibilityManager} accessibility Support tabbing interactive elements.
 * @property {Tiny.extract.CanvasExtract} extract Extract image data from renderer.
 * @property {Tiny.interaction.InteractionManager} interaction Handles mouse, touch and pointer events.
 * @property {Tiny.prepare.CanvasPrepare} prepare Pre-render display objects.
 */

/**
 * Adds a plugin to the renderer.
 *
 * @method Tiny.CanvasRenderer#registerPlugin
 * @param {string} pluginName - The name of the plugin.
 * @param {function} ctor - The constructor function or class for the plugin.
 */
pluginTarget.mixin(CanvasRenderer);
Documentation generated by JSDoc 3.4.3 on Fri Jul 09 2021 19:32:25 GMT+0800 (CST)