Source: core/renderers/canvas/utils/CanvasMaskManager.js

core/renderers/canvas/utils/CanvasMaskManager.js

  1. import { SHAPES } from '../../../const';
  2. /**
  3. * A set of functions used to handle masking.
  4. *
  5. * @class
  6. * @memberof PIXI
  7. */
  8. export default class CanvasMaskManager
  9. {
  10. /**
  11. * @param {PIXI.CanvasRenderer} renderer - The canvas renderer.
  12. */
  13. constructor(renderer)
  14. {
  15. this.renderer = renderer;
  16. }
  17. /**
  18. * This method adds it to the current stack of masks.
  19. *
  20. * @param {object} maskData - the maskData that will be pushed
  21. */
  22. pushMask(maskData)
  23. {
  24. const renderer = this.renderer;
  25. renderer.context.save();
  26. const cacheAlpha = maskData.alpha;
  27. const transform = maskData.transform.worldTransform;
  28. const resolution = renderer.resolution;
  29. renderer.context.setTransform(
  30. transform.a * resolution,
  31. transform.b * resolution,
  32. transform.c * resolution,
  33. transform.d * resolution,
  34. transform.tx * resolution,
  35. transform.ty * resolution
  36. );
  37. // TODO suport sprite alpha masks??
  38. // lots of effort required. If demand is great enough..
  39. if (!maskData._texture)
  40. {
  41. this.renderGraphicsShape(maskData);
  42. renderer.context.clip();
  43. }
  44. maskData.worldAlpha = cacheAlpha;
  45. }
  46. /**
  47. * Renders a PIXI.Graphics shape.
  48. *
  49. * @param {PIXI.Graphics} graphics - The object to render.
  50. */
  51. renderGraphicsShape(graphics)
  52. {
  53. const context = this.renderer.context;
  54. const len = graphics.graphicsData.length;
  55. if (len === 0)
  56. {
  57. return;
  58. }
  59. context.beginPath();
  60. for (let i = 0; i < len; i++)
  61. {
  62. const data = graphics.graphicsData[i];
  63. const shape = data.shape;
  64. if (data.type === SHAPES.POLY)
  65. {
  66. let points = shape.points;
  67. const holes = data.holes;
  68. let outerArea;
  69. let innerArea;
  70. context.moveTo(points[0], points[1]);
  71. for (let j = 2; j < points.length; j += 2)
  72. {
  73. context.lineTo(points[j], points[j + 1]);
  74. }
  75. // if the first and last point are the same close the path - much neater :)
  76. if (points[0] === points[points.length - 2] && points[1] === points[points.length - 1])
  77. {
  78. context.closePath();
  79. }
  80. if (holes.length > 0)
  81. {
  82. outerArea = 0;
  83. for (let j = 0; j < points.length; j += 2)
  84. {
  85. outerArea += (points[j] * points[j + 3]) - (points[j + 1] * points[j + 2]);
  86. }
  87. for (let k = 0; k < holes.length; k++)
  88. {
  89. points = holes[k].points;
  90. innerArea = 0;
  91. for (let j = 0; j < points.length; j += 2)
  92. {
  93. innerArea += (points[j] * points[j + 3]) - (points[j + 1] * points[j + 2]);
  94. }
  95. context.moveTo(points[0], points[1]);
  96. if (innerArea * outerArea < 0)
  97. {
  98. for (let j = 2; j < points.length; j += 2)
  99. {
  100. context.lineTo(points[j], points[j + 1]);
  101. }
  102. }
  103. else
  104. {
  105. for (let j = points.length - 2; j >= 2; j -= 2)
  106. {
  107. context.lineTo(points[j], points[j + 1]);
  108. }
  109. }
  110. }
  111. }
  112. }
  113. else if (data.type === SHAPES.RECT)
  114. {
  115. context.rect(shape.x, shape.y, shape.width, shape.height);
  116. context.closePath();
  117. }
  118. else if (data.type === SHAPES.CIRC)
  119. {
  120. // TODO - need to be Undefined!
  121. context.arc(shape.x, shape.y, shape.radius, 0, 2 * Math.PI);
  122. context.closePath();
  123. }
  124. else if (data.type === SHAPES.ELIP)
  125. {
  126. // ellipse code taken from: http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
  127. const w = shape.width * 2;
  128. const h = shape.height * 2;
  129. const x = shape.x - (w / 2);
  130. const y = shape.y - (h / 2);
  131. const kappa = 0.5522848;
  132. const ox = (w / 2) * kappa; // control point offset horizontal
  133. const oy = (h / 2) * kappa; // control point offset vertical
  134. const xe = x + w; // x-end
  135. const ye = y + h; // y-end
  136. const xm = x + (w / 2); // x-middle
  137. const ym = y + (h / 2); // y-middle
  138. context.moveTo(x, ym);
  139. context.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  140. context.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  141. context.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  142. context.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  143. context.closePath();
  144. }
  145. else if (data.type === SHAPES.RREC)
  146. {
  147. const rx = shape.x;
  148. const ry = shape.y;
  149. const width = shape.width;
  150. const height = shape.height;
  151. let radius = shape.radius;
  152. const maxRadius = Math.min(width, height) / 2 | 0;
  153. radius = radius > maxRadius ? maxRadius : radius;
  154. context.moveTo(rx, ry + radius);
  155. context.lineTo(rx, ry + height - radius);
  156. context.quadraticCurveTo(rx, ry + height, rx + radius, ry + height);
  157. context.lineTo(rx + width - radius, ry + height);
  158. context.quadraticCurveTo(rx + width, ry + height, rx + width, ry + height - radius);
  159. context.lineTo(rx + width, ry + radius);
  160. context.quadraticCurveTo(rx + width, ry, rx + width - radius, ry);
  161. context.lineTo(rx + radius, ry);
  162. context.quadraticCurveTo(rx, ry, rx, ry + radius);
  163. context.closePath();
  164. }
  165. }
  166. }
  167. /**
  168. * Restores the current drawing context to the state it was before the mask was applied.
  169. *
  170. * @param {PIXI.CanvasRenderer} renderer - The renderer context to use.
  171. */
  172. popMask(renderer)
  173. {
  174. renderer.context.restore();
  175. renderer.invalidateBlendMode();
  176. }
  177. /**
  178. * Destroys this canvas mask manager.
  179. *
  180. */
  181. destroy()
  182. {
  183. /* empty */
  184. }
  185. }