Source: core/sprites/Sprite.js

core/sprites/Sprite.js

  1. import { Point, ObservablePoint, Rectangle } from '../math';
  2. import { sign, TextureCache } from '../utils';
  3. import { BLEND_MODES } from '../const';
  4. import Texture from '../textures/Texture';
  5. import Container from '../display/Container';
  6. const tempPoint = new Point();
  7. /**
  8. * The Sprite object is the base for all textured objects that are rendered to the screen
  9. *
  10. * A sprite can be created directly from an image like this:
  11. *
  12. * ```js
  13. * let sprite = new PIXI.Sprite.fromImage('assets/image.png');
  14. * ```
  15. *
  16. * The more efficient way to create sprites is using a {@link PIXI.Spritesheet}:
  17. *
  18. * ```js
  19. * PIXI.loader.add("assets/spritesheet.json").load(setup);
  20. *
  21. * function setup() {
  22. * let sheet = PIXI.loader.resources["assets/spritesheet.json"].spritesheet;
  23. * let sprite = new PIXI.Sprite(sheet.textures["image.png"]);
  24. * ...
  25. * }
  26. * ```
  27. *
  28. * @class
  29. * @extends PIXI.Container
  30. * @memberof PIXI
  31. */
  32. export default class Sprite extends Container
  33. {
  34. /**
  35. * @param {PIXI.Texture} texture - The texture for this sprite
  36. */
  37. constructor(texture)
  38. {
  39. super();
  40. /**
  41. * The anchor sets the origin point of the texture.
  42. * The default is 0,0 or taken from the {@link PIXI.Texture#defaultAnchor|Texture}
  43. * passed to the constructor. A value of 0,0 means the texture's origin is the top left.
  44. * Setting the anchor to 0.5,0.5 means the texture's origin is centered.
  45. * Setting the anchor to 1,1 would mean the texture's origin point will be the bottom right corner.
  46. * Note: Updating the {@link PIXI.Texture#defaultAnchor} after a Texture is
  47. * created does _not_ update the Sprite's anchor values.
  48. *
  49. * @member {PIXI.ObservablePoint}
  50. * @private
  51. */
  52. this._anchor = new ObservablePoint(
  53. this._onAnchorUpdate,
  54. this,
  55. (texture ? texture.defaultAnchor.x : 0),
  56. (texture ? texture.defaultAnchor.y : 0)
  57. );
  58. /**
  59. * The texture that the sprite is using
  60. *
  61. * @private
  62. * @member {PIXI.Texture}
  63. */
  64. this._texture = null;
  65. /**
  66. * The width of the sprite (this is initially set by the texture)
  67. *
  68. * @private
  69. * @member {number}
  70. */
  71. this._width = 0;
  72. /**
  73. * The height of the sprite (this is initially set by the texture)
  74. *
  75. * @private
  76. * @member {number}
  77. */
  78. this._height = 0;
  79. /**
  80. * The tint applied to the sprite. This is a hex value. A value of 0xFFFFFF will remove any tint effect.
  81. *
  82. * @private
  83. * @member {number}
  84. * @default 0xFFFFFF
  85. */
  86. this._tint = null;
  87. this._tintRGB = null;
  88. this.tint = 0xFFFFFF;
  89. /**
  90. * The blend mode to be applied to the sprite. Apply a value of `PIXI.BLEND_MODES.NORMAL` to reset the blend mode.
  91. *
  92. * @member {number}
  93. * @default PIXI.BLEND_MODES.NORMAL
  94. * @see PIXI.BLEND_MODES
  95. */
  96. this.blendMode = BLEND_MODES.NORMAL;
  97. /**
  98. * The shader that will be used to render the sprite. Set to null to remove a current shader.
  99. *
  100. * @member {PIXI.Filter|PIXI.Shader}
  101. */
  102. this.shader = null;
  103. /**
  104. * An internal cached value of the tint.
  105. *
  106. * @private
  107. * @member {number}
  108. * @default 0xFFFFFF
  109. */
  110. this.cachedTint = 0xFFFFFF;
  111. // call texture setter
  112. this.texture = texture || Texture.EMPTY;
  113. /**
  114. * this is used to store the vertex data of the sprite (basically a quad)
  115. *
  116. * @private
  117. * @member {Float32Array}
  118. */
  119. this.vertexData = new Float32Array(8);
  120. /**
  121. * This is used to calculate the bounds of the object IF it is a trimmed sprite
  122. *
  123. * @private
  124. * @member {Float32Array}
  125. */
  126. this.vertexTrimmedData = null;
  127. this._transformID = -1;
  128. this._textureID = -1;
  129. this._transformTrimmedID = -1;
  130. this._textureTrimmedID = -1;
  131. /**
  132. * Plugin that is responsible for rendering this element.
  133. * Allows to customize the rendering process without overriding '_renderWebGL' & '_renderCanvas' methods.
  134. *
  135. * @member {string}
  136. * @default 'sprite'
  137. */
  138. this.pluginName = 'sprite';
  139. }
  140. /**
  141. * When the texture is updated, this event will fire to update the scale and frame
  142. *
  143. * @private
  144. */
  145. _onTextureUpdate()
  146. {
  147. this._textureID = -1;
  148. this._textureTrimmedID = -1;
  149. this.cachedTint = 0xFFFFFF;
  150. // so if _width is 0 then width was not set..
  151. if (this._width)
  152. {
  153. this.scale.x = sign(this.scale.x) * this._width / this._texture.orig.width;
  154. }
  155. if (this._height)
  156. {
  157. this.scale.y = sign(this.scale.y) * this._height / this._texture.orig.height;
  158. }
  159. }
  160. /**
  161. * Called when the anchor position updates.
  162. *
  163. * @private
  164. */
  165. _onAnchorUpdate()
  166. {
  167. this._transformID = -1;
  168. this._transformTrimmedID = -1;
  169. }
  170. /**
  171. * calculates worldTransform * vertices, store it in vertexData
  172. */
  173. calculateVertices()
  174. {
  175. if (this._transformID === this.transform._worldID && this._textureID === this._texture._updateID)
  176. {
  177. return;
  178. }
  179. this._transformID = this.transform._worldID;
  180. this._textureID = this._texture._updateID;
  181. // set the vertex data
  182. const texture = this._texture;
  183. const wt = this.transform.worldTransform;
  184. const a = wt.a;
  185. const b = wt.b;
  186. const c = wt.c;
  187. const d = wt.d;
  188. const tx = wt.tx;
  189. const ty = wt.ty;
  190. const vertexData = this.vertexData;
  191. const trim = texture.trim;
  192. const orig = texture.orig;
  193. const anchor = this._anchor;
  194. let w0 = 0;
  195. let w1 = 0;
  196. let h0 = 0;
  197. let h1 = 0;
  198. if (trim)
  199. {
  200. // if the sprite is trimmed and is not a tilingsprite then we need to add the extra
  201. // space before transforming the sprite coords.
  202. w1 = trim.x - (anchor._x * orig.width);
  203. w0 = w1 + trim.width;
  204. h1 = trim.y - (anchor._y * orig.height);
  205. h0 = h1 + trim.height;
  206. }
  207. else
  208. {
  209. w1 = -anchor._x * orig.width;
  210. w0 = w1 + orig.width;
  211. h1 = -anchor._y * orig.height;
  212. h0 = h1 + orig.height;
  213. }
  214. // xy
  215. vertexData[0] = (a * w1) + (c * h1) + tx;
  216. vertexData[1] = (d * h1) + (b * w1) + ty;
  217. // xy
  218. vertexData[2] = (a * w0) + (c * h1) + tx;
  219. vertexData[3] = (d * h1) + (b * w0) + ty;
  220. // xy
  221. vertexData[4] = (a * w0) + (c * h0) + tx;
  222. vertexData[5] = (d * h0) + (b * w0) + ty;
  223. // xy
  224. vertexData[6] = (a * w1) + (c * h0) + tx;
  225. vertexData[7] = (d * h0) + (b * w1) + ty;
  226. }
  227. /**
  228. * calculates worldTransform * vertices for a non texture with a trim. store it in vertexTrimmedData
  229. * This is used to ensure that the true width and height of a trimmed texture is respected
  230. */
  231. calculateTrimmedVertices()
  232. {
  233. if (!this.vertexTrimmedData)
  234. {
  235. this.vertexTrimmedData = new Float32Array(8);
  236. }
  237. else if (this._transformTrimmedID === this.transform._worldID && this._textureTrimmedID === this._texture._updateID)
  238. {
  239. return;
  240. }
  241. this._transformTrimmedID = this.transform._worldID;
  242. this._textureTrimmedID = this._texture._updateID;
  243. // lets do some special trim code!
  244. const texture = this._texture;
  245. const vertexData = this.vertexTrimmedData;
  246. const orig = texture.orig;
  247. const anchor = this._anchor;
  248. // lets calculate the new untrimmed bounds..
  249. const wt = this.transform.worldTransform;
  250. const a = wt.a;
  251. const b = wt.b;
  252. const c = wt.c;
  253. const d = wt.d;
  254. const tx = wt.tx;
  255. const ty = wt.ty;
  256. const w1 = -anchor._x * orig.width;
  257. const w0 = w1 + orig.width;
  258. const h1 = -anchor._y * orig.height;
  259. const h0 = h1 + orig.height;
  260. // xy
  261. vertexData[0] = (a * w1) + (c * h1) + tx;
  262. vertexData[1] = (d * h1) + (b * w1) + ty;
  263. // xy
  264. vertexData[2] = (a * w0) + (c * h1) + tx;
  265. vertexData[3] = (d * h1) + (b * w0) + ty;
  266. // xy
  267. vertexData[4] = (a * w0) + (c * h0) + tx;
  268. vertexData[5] = (d * h0) + (b * w0) + ty;
  269. // xy
  270. vertexData[6] = (a * w1) + (c * h0) + tx;
  271. vertexData[7] = (d * h0) + (b * w1) + ty;
  272. }
  273. /**
  274. *
  275. * Renders the object using the WebGL renderer
  276. *
  277. * @private
  278. * @param {PIXI.WebGLRenderer} renderer - The webgl renderer to use.
  279. */
  280. _renderWebGL(renderer)
  281. {
  282. this.calculateVertices();
  283. renderer.setObjectRenderer(renderer.plugins[this.pluginName]);
  284. renderer.plugins[this.pluginName].render(this);
  285. }
  286. /**
  287. * Renders the object using the Canvas renderer
  288. *
  289. * @private
  290. * @param {PIXI.CanvasRenderer} renderer - The renderer
  291. */
  292. _renderCanvas(renderer)
  293. {
  294. renderer.plugins[this.pluginName].render(this);
  295. }
  296. /**
  297. * Updates the bounds of the sprite.
  298. *
  299. * @private
  300. */
  301. _calculateBounds()
  302. {
  303. const trim = this._texture.trim;
  304. const orig = this._texture.orig;
  305. // First lets check to see if the current texture has a trim..
  306. if (!trim || (trim.width === orig.width && trim.height === orig.height))
  307. {
  308. // no trim! lets use the usual calculations..
  309. this.calculateVertices();
  310. this._bounds.addQuad(this.vertexData);
  311. }
  312. else
  313. {
  314. // lets calculate a special trimmed bounds...
  315. this.calculateTrimmedVertices();
  316. this._bounds.addQuad(this.vertexTrimmedData);
  317. }
  318. }
  319. /**
  320. * Gets the local bounds of the sprite object.
  321. *
  322. * @param {PIXI.Rectangle} rect - The output rectangle.
  323. * @return {PIXI.Rectangle} The bounds.
  324. */
  325. getLocalBounds(rect)
  326. {
  327. // we can do a fast local bounds if the sprite has no children!
  328. if (this.children.length === 0)
  329. {
  330. this._bounds.minX = this._texture.orig.width * -this._anchor._x;
  331. this._bounds.minY = this._texture.orig.height * -this._anchor._y;
  332. this._bounds.maxX = this._texture.orig.width * (1 - this._anchor._x);
  333. this._bounds.maxY = this._texture.orig.height * (1 - this._anchor._y);
  334. if (!rect)
  335. {
  336. if (!this._localBoundsRect)
  337. {
  338. this._localBoundsRect = new Rectangle();
  339. }
  340. rect = this._localBoundsRect;
  341. }
  342. return this._bounds.getRectangle(rect);
  343. }
  344. return super.getLocalBounds.call(this, rect);
  345. }
  346. /**
  347. * Tests if a point is inside this sprite
  348. *
  349. * @param {PIXI.Point} point - the point to test
  350. * @return {boolean} the result of the test
  351. */
  352. containsPoint(point)
  353. {
  354. this.worldTransform.applyInverse(point, tempPoint);
  355. const width = this._texture.orig.width;
  356. const height = this._texture.orig.height;
  357. const x1 = -width * this.anchor.x;
  358. let y1 = 0;
  359. if (tempPoint.x >= x1 && tempPoint.x < x1 + width)
  360. {
  361. y1 = -height * this.anchor.y;
  362. if (tempPoint.y >= y1 && tempPoint.y < y1 + height)
  363. {
  364. return true;
  365. }
  366. }
  367. return false;
  368. }
  369. /**
  370. * Destroys this sprite and optionally its texture and children
  371. *
  372. * @param {object|boolean} [options] - Options parameter. A boolean will act as if all options
  373. * have been set to that value
  374. * @param {boolean} [options.children=false] - if set to true, all the children will have their destroy
  375. * method called as well. 'options' will be passed on to those calls.
  376. * @param {boolean} [options.texture=false] - Should it destroy the current texture of the sprite as well
  377. * @param {boolean} [options.baseTexture=false] - Should it destroy the base texture of the sprite as well
  378. */
  379. destroy(options)
  380. {
  381. super.destroy(options);
  382. this._texture.off('update', this._onTextureUpdate, this);
  383. this._anchor = null;
  384. const destroyTexture = typeof options === 'boolean' ? options : options && options.texture;
  385. if (destroyTexture)
  386. {
  387. const destroyBaseTexture = typeof options === 'boolean' ? options : options && options.baseTexture;
  388. this._texture.destroy(!!destroyBaseTexture);
  389. }
  390. this._texture = null;
  391. this.shader = null;
  392. }
  393. // some helper functions..
  394. /**
  395. * Helper function that creates a new sprite based on the source you provide.
  396. * The source can be - frame id, image url, video url, canvas element, video element, base texture
  397. *
  398. * @static
  399. * @param {number|string|PIXI.BaseTexture|HTMLCanvasElement|HTMLVideoElement} source Source to create texture from
  400. * @return {PIXI.Sprite} The newly created sprite
  401. */
  402. static from(source)
  403. {
  404. return new Sprite(Texture.from(source));
  405. }
  406. /**
  407. * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId
  408. * The frame ids are created when a Texture packer file has been loaded
  409. *
  410. * @static
  411. * @param {string} frameId - The frame Id of the texture in the cache
  412. * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the frameId
  413. */
  414. static fromFrame(frameId)
  415. {
  416. const texture = TextureCache[frameId];
  417. if (!texture)
  418. {
  419. throw new Error(`The frameId "${frameId}" does not exist in the texture cache`);
  420. }
  421. return new Sprite(texture);
  422. }
  423. /**
  424. * Helper function that creates a sprite that will contain a texture based on an image url
  425. * If the image is not in the texture cache it will be loaded
  426. *
  427. * @static
  428. * @param {string} imageId - The image url of the texture
  429. * @param {boolean} [crossorigin=(auto)] - if you want to specify the cross-origin parameter
  430. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - if you want to specify the scale mode,
  431. * see {@link PIXI.SCALE_MODES} for possible values
  432. * @return {PIXI.Sprite} A new Sprite using a texture from the texture cache matching the image id
  433. */
  434. static fromImage(imageId, crossorigin, scaleMode)
  435. {
  436. return new Sprite(Texture.fromImage(imageId, crossorigin, scaleMode));
  437. }
  438. /**
  439. * The width of the sprite, setting this will actually modify the scale to achieve the value set
  440. *
  441. * @member {number}
  442. */
  443. get width()
  444. {
  445. return Math.abs(this.scale.x) * this._texture.orig.width;
  446. }
  447. set width(value) // eslint-disable-line require-jsdoc
  448. {
  449. const s = sign(this.scale.x) || 1;
  450. this.scale.x = s * value / this._texture.orig.width;
  451. this._width = value;
  452. }
  453. /**
  454. * The height of the sprite, setting this will actually modify the scale to achieve the value set
  455. *
  456. * @member {number}
  457. */
  458. get height()
  459. {
  460. return Math.abs(this.scale.y) * this._texture.orig.height;
  461. }
  462. set height(value) // eslint-disable-line require-jsdoc
  463. {
  464. const s = sign(this.scale.y) || 1;
  465. this.scale.y = s * value / this._texture.orig.height;
  466. this._height = value;
  467. }
  468. /**
  469. * The anchor sets the origin point of the texture.
  470. * The default is 0,0 or taken from the {@link PIXI.Texture|Texture} passed to the constructor.
  471. * Setting the texture at a later point of time does not change the anchor.
  472. *
  473. * 0,0 means the texture's origin is the top left, 0.5,0.5 is the center, 1,1 the bottom right corner.
  474. *
  475. * @member {PIXI.ObservablePoint}
  476. */
  477. get anchor()
  478. {
  479. return this._anchor;
  480. }
  481. set anchor(value) // eslint-disable-line require-jsdoc
  482. {
  483. this._anchor.copy(value);
  484. }
  485. /**
  486. * The tint applied to the sprite. This is a hex value.
  487. * A value of 0xFFFFFF will remove any tint effect.
  488. *
  489. * @member {number}
  490. * @default 0xFFFFFF
  491. */
  492. get tint()
  493. {
  494. return this._tint;
  495. }
  496. set tint(value) // eslint-disable-line require-jsdoc
  497. {
  498. this._tint = value;
  499. this._tintRGB = (value >> 16) + (value & 0xff00) + ((value & 0xff) << 16);
  500. }
  501. /**
  502. * The texture that the sprite is using
  503. *
  504. * @member {PIXI.Texture}
  505. */
  506. get texture()
  507. {
  508. return this._texture;
  509. }
  510. set texture(value) // eslint-disable-line require-jsdoc
  511. {
  512. if (this._texture === value)
  513. {
  514. return;
  515. }
  516. this._texture = value || Texture.EMPTY;
  517. this.cachedTint = 0xFFFFFF;
  518. this._textureID = -1;
  519. this._textureTrimmedID = -1;
  520. if (value)
  521. {
  522. // wait for the texture to load
  523. if (value.baseTexture.hasLoaded)
  524. {
  525. this._onTextureUpdate();
  526. }
  527. else
  528. {
  529. value.once('update', this._onTextureUpdate, this);
  530. }
  531. }
  532. }
  533. }