text: import Container from '../display/Container';
  import RenderTexture from '../textures/RenderTexture';
  import Texture from '../textures/Texture';
  import GraphicsData from './GraphicsData';
  import Sprite from '../sprites/Sprite';
  import { Matrix, Point, Rectangle, RoundedRectangle, Ellipse, Polygon, Circle } from '../math';
  import { hex2rgb, rgb2hex } from '../utils';
  import { SHAPES, BLEND_MODES } from '../const';
  import Bounds from '../display/Bounds';
  import bezierCurveTo from './utils/bezierCurveTo';
  import CanvasRenderer from '../renderers/canvas/CanvasRenderer';
  
  let canvasRenderer;
  const tempMatrix = new Matrix();
  const tempPoint = new Point();
  const tempColor1 = new Float32Array(4);
  const tempColor2 = new Float32Array(4);
  
  /**
   * The Graphics class contains methods used to draw primitive shapes such as lines, circles and
   * rectangles to the display, and to color and fill them.
   *
   * @class
   * @extends PIXI.Container
   * @memberof PIXI
   */
  export default class Graphics extends Container
  {
      /**
       *
       * @param {boolean} [nativeLines=false] - If true the lines will be draw using LINES instead of TRIANGLE_STRIP
       */
      constructor(nativeLines = false)
      {
          super();
  
          /**
           * The alpha value used when filling the Graphics object.
           *
           * @member {number}
           * @default 1
           */
          this.fillAlpha = 1;
  
          /**
           * The width (thickness) of any lines drawn.
           *
           * @member {number}
           * @default 0
           */
          this.lineWidth = 0;
  
          /**
           * If true the lines will be draw using LINES instead of TRIANGLE_STRIP
           *
           * @member {boolean}
           */
          this.nativeLines = nativeLines;
  
          /**
           * The color of any lines drawn.
           *
           * @member {string}
           * @default 0
           */
          this.lineColor = 0;
  
          /**
           * Graphics data
           *
           * @member {PIXI.GraphicsData[]}
           * @private
           */
          this.graphicsData = [];
  
          /**
           * The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to
           * reset the tint.
           *
           * @member {number}
           * @default 0xFFFFFF
           */
          this.tint = 0xFFFFFF;
  
          /**
           * The previous tint applied to the graphic shape. Used to compare to the current tint and
           * check if theres change.
           *
           * @member {number}
           * @private
           * @default 0xFFFFFF
           */
          this._prevTint = 0xFFFFFF;
  
          /**
           * The blend mode to be applied to the graphic shape. Apply a value of
           * `PIXI.BLEND_MODES.NORMAL` to reset the blend mode.
           *
           * @member {number}
           * @default PIXI.BLEND_MODES.NORMAL;
           * @see PIXI.BLEND_MODES
           */
          this.blendMode = BLEND_MODES.NORMAL;
  
          /**
           * Current path
           *
           * @member {PIXI.GraphicsData}
           * @private
           */
          this.currentPath = null;
  
          /**
           * Array containing some WebGL-related properties used by the WebGL renderer.
           *
           * @member {object<number, object>}
           * @private
           */
          // TODO - _webgl should use a prototype object, not a random undocumented object...
          this._webGL = {};
  
          /**
           * Whether this shape is being used as a mask.
           *
           * @member {boolean}
           */
          this.isMask = false;
  
          /**
           * The bounds' padding used for bounds calculation.
           *
           * @member {number}
           */
          this.boundsPadding = 0;
  
          /**
           * A cache of the local bounds to prevent recalculation.
           *
           * @member {PIXI.Rectangle}
           * @private
           */
          this._localBounds = new Bounds();
  
          /**
           * Used to detect if the graphics object has changed. If this is set to true then the graphics
           * object will be recalculated.
           *
           * @member {boolean}
           * @private
           */
          this.dirty = 0;
  
          /**
           * Used to detect if we need to do a fast rect check using the id compare method
           * @type {Number}
           */
          this.fastRectDirty = -1;
  
          /**
           * Used to detect if we clear the graphics webGL data
           * @type {Number}
           */
          this.clearDirty = 0;
  
          /**
           * Used to detect if we we need to recalculate local bounds
           * @type {Number}
           */
          this.boundsDirty = -1;
  
          /**
           * Used to detect if the cached sprite object needs to be updated.
           *
           * @member {boolean}
           * @private
           */
          this.cachedSpriteDirty = false;
  
          this._spriteRect = null;
          this._fastRect = false;
  
          /**
           * When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite.
           * This is useful if your graphics element does not change often, as it will speed up the rendering
           * of the object in exchange for taking up texture memory. It is also useful if you need the graphics
           * object to be anti-aliased, because it will be rendered using canvas. This is not recommended if
           * you are constantly redrawing the graphics element.
           *
           * @name cacheAsBitmap
           * @member {boolean}
           * @memberof PIXI.Graphics#
           * @default false
           */
      }
  
      /**
       * Creates a new Graphics object with the same values as this one.
       * Note that the only the properties of the object are cloned, not its transform (position,scale,etc)
       *
       * @return {PIXI.Graphics} A clone of the graphics object
       */
      clone()
      {
          const clone = new Graphics();
  
          clone.renderable = this.renderable;
          clone.fillAlpha = this.fillAlpha;
          clone.lineWidth = this.lineWidth;
          clone.lineColor = this.lineColor;
          clone.tint = this.tint;
          clone.blendMode = this.blendMode;
          clone.isMask = this.isMask;
          clone.boundsPadding = this.boundsPadding;
          clone.dirty = 0;
          clone.cachedSpriteDirty = this.cachedSpriteDirty;
  
          // copy graphics data
          for (let i = 0; i < this.graphicsData.length; ++i)
          {
              clone.graphicsData.push(this.graphicsData[i].clone());
          }
  
          clone.currentPath = clone.graphicsData[clone.graphicsData.length - 1];
  
          clone.updateLocalBounds();
  
          return clone;
      }
  
      /**
       * Specifies the line style used for subsequent calls to Graphics methods such as the lineTo()
       * method or the drawCircle() method.
       *
       * @param {number} [lineWidth=0] - width of the line to draw, will update the objects stored style
       * @param {number} [color=0] - color of the line to draw, will update the objects stored style
       * @param {number} [alpha=1] - alpha of the line to draw, will update the objects stored style
       * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
       */
      lineStyle(lineWidth = 0, color = 0, alpha = 1)
      {
          this.lineWidth = lineWidth;
          this.lineColor = color;
          this.lineAlpha = alpha;
  
          if (this.currentPath)
          {
              if (this.currentPath.shape.points.length)
              {
                  // halfway through a line? start a new one!
                  const shape = new Polygon(this.currentPath.shape.points.slice(-2));
  
                  shape.closed = false;
  
                  this.drawShape(shape);
              }
              else
              {
                  // otherwise its empty so lets just set the line properties
                  this.currentPath.lineWidth = this.lineWidth;
                  this.currentPath.lineColor = this.lineColor;
                  this.currentPath.lineAlpha = this.lineAlpha;
              }
          }
  
          return this;
      }
  
      /**
       * Moves the current drawing position to x, y.
       *
       * @param {number} x - the X coordinate to move to
       * @param {number} y - the Y coordinate to move to
       * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
       */
      moveTo(x, y)
      {
          const shape = new Polygon([x, y]);
  
          shape.closed = false;
          this.drawShape(shape);
  
          return this;
      }
  
      /**
       * Draws a line using the current line style from the current drawing position to (x, y);
       * The current drawing position is then set to (x, y).
       *
       * @param {number} x - the X coordinate to draw to
       * @param {number} y - the Y coordinate to draw to
       * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
       */
      lineTo(x, y)
      {
          this.currentPath.shape.points.push(x, y);
          this.dirty++;
  
          return this;
      }
  
      /**
       * Calculate the points for a quadratic bezier curve and then draws it.
       * Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c
       *
       * @param {number} cpX - Control point x
       * @param {number} cpY - Control point y
       * @param {number} toX - Destination point x
       * @param {number} toY - Destination point y
       * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
       */
      quadraticCurveTo(cpX, cpY, toX, toY)
      {
          if (this.currentPath)
          {
              if (this.currentPath.shape.points.length === 0)
              {
                  this.currentPath.shape.points = [0, 0];
              }
          }
          else
          {
              this.moveTo(0, 0);
          }
  
          const n = 20;
          const points = this.currentPath.shape.points;
          let xa = 0;
          let ya = 0;
  
          if (points.length === 0)
          {
              this.moveTo(0, 0);
          }
  
          const fromX = points[points.length - 2];
          const fromY = points[points.length - 1];
  
          for (let i = 1; i <= n; ++i)
          {
              const j = i / n;
  
              xa = fromX + ((cpX - fromX) * j);
              ya = fromY + ((cpY - fromY) * j);
  
              points.push(xa + (((cpX + ((toX - cpX) * j)) - xa) * j),
                  ya + (((cpY + ((toY - cpY) * j)) - ya) * j));
          }
  
          this.dirty++;
  
          return this;
      }
  
      /**
       * Calculate the points for a bezier curve and then draws it.
       *
       * @param {number} cpX - Control point x
       * @param {number} cpY - Control point y
       * @param {number} cpX2 - Second Control point x
       * @param {number} cpY2 - Second Control point y
       * @param {number} toX - Destination point x
       * @param {number} toY - Destination point y
       * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
       */
      bezierCurveTo(cpX, cpY, cpX2, cpY2, toX, toY)
      {
          if (this.currentPath)
          {
              if (this.currentPath.shape.points.length === 0)
              {
                  this.currentPath.shape.points = [0, 0];
              }
          }
          else
          {
              this.moveTo(0, 0);
          }
  
          const points = this.currentPath.shape.points;
  
          const fromX = points[points.length - 2];
          const fromY = points[points.length - 1];
  
          points.length -= 2;
  
          bezierCurveTo(fromX, fromY, cpX, cpY, cpX2, cpY2, toX, toY, points);
  
          this.dirty++;
  
          return this;
      }
  
      /**
       * The arcTo() method creates an arc/curve between two tangents on the canvas.
       *
       * "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google!
       *
       * @param {number} x1 - The x-coordinate of the beginning of the arc
       * @param {number} y1 - The y-coordinate of the beginning of the arc
       * @param {number} x2 - The x-coordinate of the end of the arc
       * @param {number} y2 - The y-coordinate of the end of the arc
       * @param {number} radius - The radius of the arc
       * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
       */
      arcTo(x1, y1, x2, y2, radius)
      {
          if (this.currentPath)
          {
              if (this.currentPath.shape.points.length === 0)
              {
                  this.currentPath.shape.points.push(x1, y1);
              }
          }
          else
          {
              this.moveTo(x1, y1);
          }
  
          const points = this.currentPath.shape.points;
          const fromX = points[points.length - 2];
          const fromY = points[points.length - 1];
          const a1 = fromY - y1;
          const b1 = fromX - x1;
          const a2 = y2 - y1;
          const b2 = x2 - x1;
          const mm = Math.abs((a1 * b2) - (b1 * a2));
  
          if (mm < 1.0e-8 || radius === 0)
          {
              if (points[points.length - 2] !== x1 || points[points.length - 1] !== y1)
              {
                  points.push(x1, y1);
              }
          }
          else
          {
              const dd = (a1 * a1) + (b1 * b1);
              const cc = (a2 * a2) + (b2 * b2);
              const tt = (a1 * a2) + (b1 * b2);
              const k1 = radius * Math.sqrt(dd) / mm;
              const k2 = radius * Math.sqrt(cc) / mm;
              const j1 = k1 * tt / dd;
              const j2 = k2 * tt / cc;
              const cx = (k1 * b2) + (k2 * b1);
              const cy = (k1 * a2) + (k2 * a1);
              const px = b1 * (k2 + j1);
              const py = a1 * (k2 + j1);
              const qx = b2 * (k1 + j2);
              const qy = a2 * (k1 + j2);
              const startAngle = Math.atan2(py - cy, px - cx);
              const endAngle = Math.atan2(qy - cy, qx - cx);
  
              this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1);
          }
  
          this.dirty++;
  
          return this;
      }
  
      /**
       * The arc method creates an arc/curve (used to create circles, or parts of circles).
       *
       * @param {number} cx - The x-coordinate of the center of the circle
       * @param {number} cy - The y-coordinate of the center of the circle
       * @param {number} radius - The radius of the circle
       * @param {number} startAngle - The starting angle, in radians (0 is at the 3 o'clock position
       *  of the arc's circle)
       * @param {number} endAngle - The ending angle, in radians
       * @param {boolean} [anticlockwise=false] - Specifies whether the drawing should be
       *  counter-clockwise or clockwise. False is default, and indicates clockwise, while true
       *  indicates counter-clockwise.
       * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
       */
      arc(cx, cy, radius, startAngle, endAngle, anticlockwise = false)
      {
          if (startAngle === endAngle)
          {
              return this;
          }
  
          if (!anticlockwise && endAngle <= startAngle)
          {
              endAngle += Math.PI * 2;
          }
          else if (anticlockwise && startAngle <= endAngle)
          {
              startAngle += Math.PI * 2;
          }
  
          const sweep = endAngle - startAngle;
          const segs = Math.ceil(Math.abs(sweep) / (Math.PI * 2)) * 40;
  
          if (sweep === 0)
          {
              return this;
          }
  
          const startX = cx + (Math.cos(startAngle) * radius);
          const startY = cy + (Math.sin(startAngle) * radius);
  
          // If the currentPath exists, take its points. Otherwise call `moveTo` to start a path.
          let points = this.currentPath ? this.currentPath.shape.points : null;
  
          if (points)
          {
              if (points[points.length - 2] !== startX || points[points.length - 1] !== startY)
              {
                  points.push(startX, startY);
              }
          }
          else
          {
              this.moveTo(startX, startY);
              points = this.currentPath.shape.points;
          }
  
          const theta = sweep / (segs * 2);
          const theta2 = theta * 2;
  
          const cTheta = Math.cos(theta);
          const sTheta = Math.sin(theta);
  
          const segMinus = segs - 1;
  
          const remainder = (segMinus % 1) / segMinus;
  
          for (let i = 0; i <= segMinus; ++i)
          {
              const real = i + (remainder * i);
  
              const angle = ((theta) + startAngle + (theta2 * real));
  
              const c = Math.cos(angle);
              const s = -Math.sin(angle);
  
              points.push(
                  (((cTheta * c) + (sTheta * s)) * radius) + cx,
                  (((cTheta * -s) + (sTheta * c)) * radius) + cy
              );
          }
  
          this.dirty++;
  
          return this;
      }
  
      /**
       * Specifies a simple one-color fill that subsequent calls to other Graphics methods
       * (such as lineTo() or drawCircle()) use when drawing.
       *
       * @param {number} [color=0] - the color of the fill
       * @param {number} [alpha=1] - the alpha of the fill
       * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
       */
      beginFill(color = 0, alpha = 1)
      {
          this.filling = true;
          this.fillColor = color;
          this.fillAlpha = alpha;
  
          if (this.currentPath)
          {
              if (this.currentPath.shape.points.length <= 2)
              {
                  this.currentPath.fill = this.filling;
                  this.currentPath.fillColor = this.fillColor;
                  this.currentPath.fillAlpha = this.fillAlpha;
              }
          }
  
          return this;
      }
  
      /**
       * Applies a fill to the lines and shapes that were added since the last call to the beginFill() method.
       *
       * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
       */
      endFill()
      {
          this.filling = false;
          this.fillColor = null;
          this.fillAlpha = 1;
  
          return this;
      }
  
      /**
       *
       * @param {number} x - The X coord of the top-left of the rectangle
       * @param {number} y - The Y coord of the top-left of the rectangle
       * @param {number} width - The width of the rectangle
       * @param {number} height - The height of the rectangle
       * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
       */
      drawRect(x, y, width, height)
      {
          this.drawShape(new Rectangle(x, y, width, height));
  
          return this;
      }
  
      /**
       *
       * @param {number} x - The X coord of the top-left of the rectangle
       * @param {number} y - The Y coord of the top-left of the rectangle
       * @param {number} width - The width of the rectangle
       * @param {number} height - The height of the rectangle
       * @param {number} radius - Radius of the rectangle corners
       * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
       */
      drawRoundedRect(x, y, width, height, radius)
      {
          this.drawShape(new RoundedRectangle(x, y, width, height, radius));
  
          return this;
      }
  
      /**
       * Draws a circle.
       *
       * @param {number} x - The X coordinate of the center of the circle
       * @param {number} y - The Y coordinate of the center of the circle
       * @param {number} radius - The radius of the circle
       * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
       */
      drawCircle(x, y, radius)
      {
          this.drawShape(new Circle(x, y, radius));
  
          return this;
      }
  
      /**
       * Draws an ellipse.
       *
       * @param {number} x - The X coordinate of the center of the ellipse
       * @param {number} y - The Y coordinate of the center of the ellipse
       * @param {number} width - The half width of the ellipse
       * @param {number} height - The half height of the ellipse
       * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
       */
      drawEllipse(x, y, width, height)
      {
          this.drawShape(new Ellipse(x, y, width, height));
  
          return this;
      }
  
      /**
       * Draws a polygon using the given path.
       *
       * @param {number[]|PIXI.Point[]} path - The path data used to construct the polygon.
       * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
       */
      drawPolygon(path)
      {
          // prevents an argument assignment deopt
          // see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments
          let points = path;
  
          let closed = true;
  
          if (points instanceof Polygon)
          {
              closed = points.closed;
              points = points.points;
          }
  
          if (!Array.isArray(points))
          {
              // prevents an argument leak deopt
              // see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments
              points = new Array(arguments.length);
  
              for (let i = 0; i < points.length; ++i)
              {
                  points[i] = arguments[i]; // eslint-disable-line prefer-rest-params
              }
          }
  
          const shape = new Polygon(points);
  
          shape.closed = closed;
  
          this.drawShape(shape);
  
          return this;
      }
  
      /**
       * Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings.
       *
       * @return {PIXI.Graphics} This Graphics object. Good for chaining method calls
       */
      clear()
      {
          if (this.lineWidth || this.filling || this.graphicsData.length > 0)
          {
              this.lineWidth = 0;
              this.filling = false;
  
              this.boundsDirty = -1;
              this.dirty++;
              this.clearDirty++;
              this.graphicsData.length = 0;
          }
  
          this.currentPath = null;
          this._spriteRect = null;
  
          return this;
      }
  
      /**
       * True if graphics consists of one rectangle, and thus, can be drawn like a Sprite and
       * masked with gl.scissor.
       *
       * @returns {boolean} True if only 1 rect.
       */
      isFastRect()
      {
          return this.graphicsData.length === 1
              && this.graphicsData[0].shape.type === SHAPES.RECT
              && !this.graphicsData[0].lineWidth;
      }
  
      /**
       * Renders the object using the WebGL renderer
       *
       * @private
       * @param {PIXI.WebGLRenderer} renderer - The renderer
       */
      _renderWebGL(renderer)
      {
          // if the sprite is not visible or the alpha is 0 then no need to render this element
          if (this.dirty !== this.fastRectDirty)
          {
              this.fastRectDirty = this.dirty;
              this._fastRect = this.isFastRect();
          }
  
          // TODO this check can be moved to dirty?
          if (this._fastRect)
          {
              this._renderSpriteRect(renderer);
          }
          else
          {
              renderer.setObjectRenderer(renderer.plugins.graphics);
              renderer.plugins.graphics.render(this);
          }
      }
  
      /**
       * Renders a sprite rectangle.
       *
       * @private
       * @param {PIXI.WebGLRenderer} renderer - The renderer
       */
      _renderSpriteRect(renderer)
      {
          const rect = this.graphicsData[0].shape;
  
          if (!this._spriteRect)
          {
              this._spriteRect = new Sprite(new Texture(Texture.WHITE));
          }
  
          const sprite = this._spriteRect;
  
          if (this.tint === 0xffffff)
          {
              sprite.tint = this.graphicsData[0].fillColor;
          }
          else
          {
              const t1 = tempColor1;
              const t2 = tempColor2;
  
              hex2rgb(this.graphicsData[0].fillColor, t1);
              hex2rgb(this.tint, t2);
  
              t1[0] *= t2[0];
              t1[1] *= t2[1];
              t1[2] *= t2[2];
  
              sprite.tint = rgb2hex(t1);
          }
          sprite.alpha = this.graphicsData[0].fillAlpha;
          sprite.worldAlpha = this.worldAlpha * sprite.alpha;
          sprite.blendMode = this.blendMode;
  
          sprite.texture._frame.width = rect.width;
          sprite.texture._frame.height = rect.height;
  
          sprite.transform.worldTransform = this.transform.worldTransform;
  
          sprite.anchor.set(-rect.x / rect.width, -rect.y / rect.height);
          sprite._onAnchorUpdate();
  
          sprite._renderWebGL(renderer);
      }
  
      /**
       * Renders the object using the Canvas renderer
       *
       * @private
       * @param {PIXI.CanvasRenderer} renderer - The renderer
       */
      _renderCanvas(renderer)
      {
          if (this.isMask === true)
          {
              return;
          }
  
          renderer.plugins.graphics.render(this);
      }
  
      /**
       * Retrieves the bounds of the graphic shape as a rectangle object
       *
       * @private
       */
      _calculateBounds()
      {
          if (this.boundsDirty !== this.dirty)
          {
              this.boundsDirty = this.dirty;
              this.updateLocalBounds();
  
              this.cachedSpriteDirty = true;
          }
  
          const lb = this._localBounds;
  
          this._bounds.addFrame(this.transform, lb.minX, lb.minY, lb.maxX, lb.maxY);
      }
  
      /**
       * Tests if a point is inside this graphics object
       *
       * @param {PIXI.Point} point - the point to test
       * @return {boolean} the result of the test
       */
      containsPoint(point)
      {
          this.worldTransform.applyInverse(point, tempPoint);
  
          const graphicsData = this.graphicsData;
  
          for (let i = 0; i < graphicsData.length; ++i)
          {
              const data = graphicsData[i];
  
              if (!data.fill)
              {
                  continue;
              }
  
              // only deal with fills..
              if (data.shape)
              {
                  if (data.shape.contains(tempPoint.x, tempPoint.y))
                  {
                      return true;
                  }
              }
          }
  
          return false;
      }
  
      /**
       * Update the bounds of the object
       *
       */
      updateLocalBounds()
      {
          let minX = Infinity;
          let maxX = -Infinity;
  
          let minY = Infinity;
          let maxY = -Infinity;
  
          if (this.graphicsData.length)
          {
              let shape = 0;
              let x = 0;
              let y = 0;
              let w = 0;
              let h = 0;
  
              for (let i = 0; i < this.graphicsData.length; i++)
              {
                  const data = this.graphicsData[i];
                  const type = data.type;
                  const lineWidth = data.lineWidth;
  
                  shape = data.shape;
  
                  if (type === SHAPES.RECT || type === SHAPES.RREC)
                  {
                      x = shape.x - (lineWidth / 2);
                      y = shape.y - (lineWidth / 2);
                      w = shape.width + lineWidth;
                      h = shape.height + lineWidth;
  
                      minX = x < minX ? x : minX;
                      maxX = x + w > maxX ? x + w : maxX;
  
                      minY = y < minY ? y : minY;
                      maxY = y + h > maxY ? y + h : maxY;
                  }
                  else if (type === SHAPES.CIRC)
                  {
                      x = shape.x;
                      y = shape.y;
                      w = shape.radius + (lineWidth / 2);
                      h = shape.radius + (lineWidth / 2);
  
                      minX = x - w < minX ? x - w : minX;
                      maxX = x + w > maxX ? x + w : maxX;
  
                      minY = y - h < minY ? y - h : minY;
                      maxY = y + h > maxY ? y + h : maxY;
                  }
                  else if (type === SHAPES.ELIP)
                  {
                      x = shape.x;
                      y = shape.y;
                      w = shape.width + (lineWidth / 2);
                      h = shape.height + (lineWidth / 2);
  
                      minX = x - w < minX ? x - w : minX;
                      maxX = x + w > maxX ? x + w : maxX;
  
                      minY = y - h < minY ? y - h : minY;
                      maxY = y + h > maxY ? y + h : maxY;
                  }
                  else
                  {
                      // POLY
                      const points = shape.points;
                      let x2 = 0;
                      let y2 = 0;
                      let dx = 0;
                      let dy = 0;
                      let rw = 0;
                      let rh = 0;
                      let cx = 0;
                      let cy = 0;
  
                      for (let j = 0; j + 2 < points.length; j += 2)
                      {
                          x = points[j];
                          y = points[j + 1];
                          x2 = points[j + 2];
                          y2 = points[j + 3];
                          dx = Math.abs(x2 - x);
                          dy = Math.abs(y2 - y);
                          h = lineWidth;
                          w = Math.sqrt((dx * dx) + (dy * dy));
  
                          if (w < 1e-9)
                          {
                              continue;
                          }
  
                          rw = ((h / w * dy) + dx) / 2;
                          rh = ((h / w * dx) + dy) / 2;
                          cx = (x2 + x) / 2;
                          cy = (y2 + y) / 2;
  
                          minX = cx - rw < minX ? cx - rw : minX;
                          maxX = cx + rw > maxX ? cx + rw : maxX;
  
                          minY = cy - rh < minY ? cy - rh : minY;
                          maxY = cy + rh > maxY ? cy + rh : maxY;
                      }
                  }
              }
          }
          else
          {
              minX = 0;
              maxX = 0;
              minY = 0;
              maxY = 0;
          }
  
          const padding = this.boundsPadding;
  
          this._localBounds.minX = minX - padding;
          this._localBounds.maxX = maxX + (padding * 2);
  
          this._localBounds.minY = minY - padding;
          this._localBounds.maxY = maxY + (padding * 2);
      }
  
      /**
       * Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon.
       *
       * @param {PIXI.Circle|PIXI.Ellipse|PIXI.Polygon|PIXI.Rectangle|PIXI.RoundedRectangle} shape - The shape object to draw.
       * @return {PIXI.GraphicsData} The generated GraphicsData object.
       */
      drawShape(shape)
      {
          if (this.currentPath)
          {
              // check current path!
              if (this.currentPath.shape.points.length <= 2)
              {
                  this.graphicsData.pop();
              }
          }
  
          this.currentPath = null;
  
          const data = new GraphicsData(
              this.lineWidth,
              this.lineColor,
              this.lineAlpha,
              this.fillColor,
              this.fillAlpha,
              this.filling,
              this.nativeLines,
              shape
          );
  
          this.graphicsData.push(data);
  
          if (data.type === SHAPES.POLY)
          {
              data.shape.closed = data.shape.closed || this.filling;
              this.currentPath = data;
          }
  
          this.dirty++;
  
          return data;
      }
  
      /**
       * Generates a canvas texture.
       *
       * @param {number} scaleMode - The scale mode of the texture.
       * @param {number} resolution - The resolution of the texture.
       * @return {PIXI.Texture} The new texture.
       */
      generateCanvasTexture(scaleMode, resolution = 1)
      {
          const bounds = this.getLocalBounds();
  
          const canvasBuffer = RenderTexture.create(bounds.width, bounds.height, scaleMode, resolution);
  
          if (!canvasRenderer)
          {
              canvasRenderer = new CanvasRenderer();
          }
  
          this.transform.updateLocalTransform();
          this.transform.localTransform.copy(tempMatrix);
  
          tempMatrix.invert();
  
          tempMatrix.tx -= bounds.x;
          tempMatrix.ty -= bounds.y;
  
          canvasRenderer.render(this, canvasBuffer, true, tempMatrix);
  
          const texture = Texture.fromCanvas(canvasBuffer.baseTexture._canvasRenderTarget.canvas, scaleMode);
  
          texture.baseTexture.resolution = resolution;
          texture.baseTexture.update();
  
          return texture;
      }
  
      /**
       * Closes the current path.
       *
       * @return {PIXI.Graphics} Returns itself.
       */
      closePath()
      {
          // ok so close path assumes next one is a hole!
          const currentPath = this.currentPath;
  
          if (currentPath && currentPath.shape)
          {
              currentPath.shape.close();
          }
  
          return this;
      }
  
      /**
       * Adds a hole in the current path.
       *
       * @return {PIXI.Graphics} Returns itself.
       */
      addHole()
      {
          // this is a hole!
          const hole = this.graphicsData.pop();
  
          this.currentPath = this.graphicsData[this.graphicsData.length - 1];
  
          this.currentPath.addHole(hole.shape);
          this.currentPath = null;
  
          return this;
      }
  
      /**
       * Destroys the Graphics object.
       *
       * @param {object|boolean} [options] - Options parameter. A boolean will act as if all
       *  options have been set to that value
       * @param {boolean} [options.children=false] - if set to true, all the children will have
       *  their destroy method called as well. 'options' will be passed on to those calls.
       * @param {boolean} [options.texture=false] - Only used for child Sprites if options.children is set to true
       *  Should it destroy the texture of the child sprite
       * @param {boolean} [options.baseTexture=false] - Only used for child Sprites if options.children is set to true
       *  Should it destroy the base texture of the child sprite
       */
      destroy(options)
      {
          super.destroy(options);
  
          // destroy each of the GraphicsData objects
          for (let i = 0; i < this.graphicsData.length; ++i)
          {
              this.graphicsData[i].destroy();
          }
  
          // for each webgl data entry, destroy the WebGLGraphicsData
          for (const id in this._webgl)
          {
              for (let j = 0; j < this._webgl[id].data.length; ++j)
              {
                  this._webgl[id].data[j].destroy();
              }
          }
  
          if (this._spriteRect)
          {
              this._spriteRect.destroy();
          }
  
          this.graphicsData = null;
  
          this.currentPath = null;
          this._webgl = null;
          this._localBounds = null;
      }
  
  }
  
  Graphics._SPRITE_TEXTURE = null;
  