Source: core/textures/Texture.js

core/textures/Texture.js

  1. import BaseTexture from './BaseTexture';
  2. import VideoBaseTexture from './VideoBaseTexture';
  3. import TextureUvs from './TextureUvs';
  4. import EventEmitter from 'eventemitter3';
  5. import { Rectangle, Point } from '../math';
  6. import { TextureCache, getResolutionOfUrl } from '../utils';
  7. import settings from '../settings';
  8. //引入小程序补丁
  9. import { documentAlias, HTMLCanvasElement, HTMLVideoElement } from '@ali/pixi-miniprogram-adapter';
  10. /**
  11. * A texture stores the information that represents an image or part of an image. It cannot be added
  12. * to the display list directly. Instead use it as the texture for a Sprite. If no frame is provided
  13. * then the whole image is used.
  14. *
  15. * You can directly create a texture from an image and then reuse it multiple times like this :
  16. *
  17. * ```js
  18. * let texture = PIXI.Texture.fromImage('assets/image.png');
  19. * let sprite1 = new PIXI.Sprite(texture);
  20. * let sprite2 = new PIXI.Sprite(texture);
  21. * ```
  22. *
  23. * Textures made from SVGs, loaded or not, cannot be used before the file finishes processing.
  24. * You can check for this by checking the sprite's _textureID property.
  25. * ```js
  26. * var texture = PIXI.Texture.fromImage('assets/image.svg');
  27. * var sprite1 = new PIXI.Sprite(texture);
  28. * //sprite1._textureID should not be undefined if the texture has finished processing the SVG file
  29. * ```
  30. * You can use a ticker or rAF to ensure your sprites load the finished textures after processing. See issue #3068.
  31. *
  32. * @class
  33. * @extends EventEmitter
  34. * @memberof PIXI
  35. */
  36. export default class Texture extends EventEmitter
  37. {
  38. /**
  39. * @param {PIXI.BaseTexture} baseTexture - The base texture source to create the texture from
  40. * @param {PIXI.Rectangle} [frame] - The rectangle frame of the texture to show
  41. * @param {PIXI.Rectangle} [orig] - The area of original texture
  42. * @param {PIXI.Rectangle} [trim] - Trimmed rectangle of original texture
  43. * @param {number} [rotate] - indicates how the texture was rotated by texture packer. See {@link PIXI.GroupD8}
  44. * @param {PIXI.Point} [anchor] - Default anchor point used for sprite placement / rotation
  45. */
  46. constructor(baseTexture, frame, orig, trim, rotate, anchor)
  47. {
  48. super();
  49. /**
  50. * Does this Texture have any frame data assigned to it?
  51. *
  52. * @member {boolean}
  53. */
  54. this.noFrame = false;
  55. if (!frame)
  56. {
  57. this.noFrame = true;
  58. frame = new Rectangle(0, 0, 1, 1);
  59. }
  60. if (baseTexture instanceof Texture)
  61. {
  62. baseTexture = baseTexture.baseTexture;
  63. }
  64. /**
  65. * The base texture that this texture uses.
  66. *
  67. * @member {PIXI.BaseTexture}
  68. */
  69. this.baseTexture = baseTexture;
  70. /**
  71. * This is the area of the BaseTexture image to actually copy to the Canvas / WebGL when rendering,
  72. * irrespective of the actual frame size or placement (which can be influenced by trimmed texture atlases)
  73. *
  74. * @member {PIXI.Rectangle}
  75. */
  76. this._frame = frame;
  77. /**
  78. * This is the trimmed area of original texture, before it was put in atlas
  79. * Please call `_updateUvs()` after you change coordinates of `trim` manually.
  80. *
  81. * @member {PIXI.Rectangle}
  82. */
  83. this.trim = trim;
  84. /**
  85. * This will let the renderer know if the texture is valid. If it's not then it cannot be rendered.
  86. *
  87. * @member {boolean}
  88. */
  89. this.valid = false;
  90. /**
  91. * This will let a renderer know that a texture has been updated (used mainly for webGL uv updates)
  92. *
  93. * @member {boolean}
  94. */
  95. this.requiresUpdate = false;
  96. /**
  97. * The WebGL UV data cache.
  98. *
  99. * @member {PIXI.TextureUvs}
  100. * @private
  101. */
  102. this._uvs = null;
  103. /**
  104. * This is the area of original texture, before it was put in atlas
  105. *
  106. * @member {PIXI.Rectangle}
  107. */
  108. this.orig = orig || frame;// new Rectangle(0, 0, 1, 1);
  109. this._rotate = Number(rotate || 0);
  110. if (rotate === true)
  111. {
  112. // this is old texturepacker legacy, some games/libraries are passing "true" for rotated textures
  113. this._rotate = 2;
  114. }
  115. else if (this._rotate % 2 !== 0)
  116. {
  117. throw new Error('attempt to use diamond-shaped UVs. If you are sure, set rotation manually');
  118. }
  119. if (baseTexture.hasLoaded)
  120. {
  121. if (this.noFrame)
  122. {
  123. frame = new Rectangle(0, 0, baseTexture.width, baseTexture.height);
  124. // if there is no frame we should monitor for any base texture changes..
  125. baseTexture.on('update', this.onBaseTextureUpdated, this);
  126. }
  127. this.frame = frame;
  128. }
  129. else
  130. {
  131. baseTexture.once('loaded', this.onBaseTextureLoaded, this);
  132. }
  133. /**
  134. * Anchor point that is used as default if sprite is created with this texture.
  135. * Changing the `defaultAnchor` at a later point of time will not update Sprite's anchor point.
  136. * @member {PIXI.Point}
  137. * @default {0,0}
  138. */
  139. this.defaultAnchor = anchor ? new Point(anchor.x, anchor.y) : new Point(0, 0);
  140. /**
  141. * Fired when the texture is updated. This happens if the frame or the baseTexture is updated.
  142. *
  143. * @event PIXI.Texture#update
  144. * @protected
  145. * @param {PIXI.Texture} texture - Instance of texture being updated.
  146. */
  147. this._updateID = 0;
  148. /**
  149. * Contains data for uvs. May contain clamp settings and some matrices.
  150. * Its a bit heavy, so by default that object is not created.
  151. * @member {PIXI.TextureMatrix}
  152. * @default null
  153. */
  154. this.transform = null;
  155. /**
  156. * The ids under which this Texture has been added to the texture cache. This is
  157. * automatically set as long as Texture.addToCache is used, but may not be set if a
  158. * Texture is added directly to the TextureCache array.
  159. *
  160. * @member {string[]}
  161. */
  162. this.textureCacheIds = [];
  163. }
  164. /**
  165. * Updates this texture on the gpu.
  166. *
  167. */
  168. update()
  169. {
  170. this.baseTexture.update();
  171. }
  172. /**
  173. * Called when the base texture is loaded
  174. *
  175. * @private
  176. * @param {PIXI.BaseTexture} baseTexture - The base texture.
  177. */
  178. onBaseTextureLoaded(baseTexture)
  179. {
  180. this._updateID++;
  181. // TODO this code looks confusing.. boo to abusing getters and setters!
  182. if (this.noFrame)
  183. {
  184. this.frame = new Rectangle(0, 0, baseTexture.width, baseTexture.height);
  185. }
  186. else
  187. {
  188. this.frame = this._frame;
  189. }
  190. this.baseTexture.on('update', this.onBaseTextureUpdated, this);
  191. this.emit('update', this);
  192. }
  193. /**
  194. * Called when the base texture is updated
  195. *
  196. * @private
  197. * @param {PIXI.BaseTexture} baseTexture - The base texture.
  198. */
  199. onBaseTextureUpdated(baseTexture)
  200. {
  201. this._updateID++;
  202. this._frame.width = baseTexture.width;
  203. this._frame.height = baseTexture.height;
  204. this.emit('update', this);
  205. }
  206. /**
  207. * Destroys this texture
  208. *
  209. * @param {boolean} [destroyBase=false] Whether to destroy the base texture as well
  210. */
  211. destroy(destroyBase)
  212. {
  213. if (this.baseTexture)
  214. {
  215. if (destroyBase)
  216. {
  217. // delete the texture if it exists in the texture cache..
  218. // this only needs to be removed if the base texture is actually destroyed too..
  219. if (TextureCache[this.baseTexture.imageUrl])
  220. {
  221. Texture.removeFromCache(this.baseTexture.imageUrl);
  222. }
  223. this.baseTexture.destroy();
  224. }
  225. this.baseTexture.off('update', this.onBaseTextureUpdated, this);
  226. this.baseTexture.off('loaded', this.onBaseTextureLoaded, this);
  227. this.baseTexture = null;
  228. }
  229. this._frame = null;
  230. this._uvs = null;
  231. this.trim = null;
  232. this.orig = null;
  233. this.valid = false;
  234. Texture.removeFromCache(this);
  235. this.textureCacheIds = null;
  236. }
  237. /**
  238. * Creates a new texture object that acts the same as this one.
  239. *
  240. * @return {PIXI.Texture} The new texture
  241. */
  242. clone()
  243. {
  244. return new Texture(this.baseTexture, this.frame, this.orig, this.trim, this.rotate, this.defaultAnchor);
  245. }
  246. /**
  247. * Updates the internal WebGL UV cache. Use it after you change `frame` or `trim` of the texture.
  248. */
  249. _updateUvs()
  250. {
  251. if (!this._uvs)
  252. {
  253. this._uvs = new TextureUvs();
  254. }
  255. this._uvs.set(this._frame, this.baseTexture, this.rotate);
  256. this._updateID++;
  257. }
  258. /**
  259. * Helper function that creates a Texture object from the given image url.
  260. * If the image is not in the texture cache it will be created and loaded.
  261. *
  262. * @static
  263. * @param {string} imageUrl - The image url of the texture
  264. * @param {boolean} [crossorigin] - Whether requests should be treated as crossorigin
  265. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values
  266. * @param {number} [sourceScale=(auto)] - Scale for the original image, used with SVG images.
  267. * @return {PIXI.Texture} The newly created texture
  268. */
  269. static fromImage(imageUrl, crossorigin, scaleMode, sourceScale)
  270. {
  271. let texture = TextureCache[imageUrl];
  272. if (!texture)
  273. {
  274. texture = new Texture(BaseTexture.fromImage(imageUrl, crossorigin, scaleMode, sourceScale));
  275. Texture.addToCache(texture, imageUrl);
  276. }
  277. return texture;
  278. }
  279. /**
  280. * Helper function that creates a sprite that will contain a texture from the TextureCache based on the frameId
  281. * The frame ids are created when a Texture packer file has been loaded
  282. *
  283. * @static
  284. * @param {string} frameId - The frame Id of the texture in the cache
  285. * @return {PIXI.Texture} The newly created texture
  286. */
  287. static fromFrame(frameId)
  288. {
  289. const texture = TextureCache[frameId];
  290. if (!texture)
  291. {
  292. throw new Error(`The frameId "${frameId}" does not exist in the texture cache`);
  293. }
  294. return texture;
  295. }
  296. /**
  297. * Helper function that creates a new Texture based on the given canvas element.
  298. *
  299. * @static
  300. * @param {HTMLCanvasElement} canvas - The canvas element source of the texture
  301. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values
  302. * @param {string} [origin='canvas'] - A string origin of who created the base texture
  303. * @return {PIXI.Texture} The newly created texture
  304. */
  305. static fromCanvas(canvas, scaleMode, origin = 'canvas')
  306. {
  307. return new Texture(BaseTexture.fromCanvas(canvas, scaleMode, origin));
  308. }
  309. /**
  310. * Helper function that creates a new Texture based on the given video element.
  311. *
  312. * @static
  313. * @param {HTMLVideoElement|string} video - The URL or actual element of the video
  314. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values
  315. * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI.
  316. * @param {boolean} [autoPlay=true] - Start playing video as soon as it is loaded
  317. * @return {PIXI.Texture} The newly created texture
  318. */
  319. static fromVideo(video, scaleMode, crossorigin, autoPlay)
  320. {
  321. if (typeof video === 'string')
  322. {
  323. return Texture.fromVideoUrl(video, scaleMode, crossorigin, autoPlay);
  324. }
  325. return new Texture(VideoBaseTexture.fromVideo(video, scaleMode, autoPlay));
  326. }
  327. /**
  328. * Helper function that creates a new Texture based on the video url.
  329. *
  330. * @static
  331. * @param {string} videoUrl - URL of the video
  332. * @param {number} [scaleMode=PIXI.settings.SCALE_MODE] - See {@link PIXI.SCALE_MODES} for possible values
  333. * @param {boolean} [crossorigin=(auto)] - Should use anonymous CORS? Defaults to true if the URL is not a data-URI.
  334. * @param {boolean} [autoPlay=true] - Start playing video as soon as it is loaded
  335. * @return {PIXI.Texture} The newly created texture
  336. */
  337. static fromVideoUrl(videoUrl, scaleMode, crossorigin, autoPlay)
  338. {
  339. return new Texture(VideoBaseTexture.fromUrl(videoUrl, scaleMode, crossorigin, autoPlay));
  340. }
  341. /**
  342. * Helper function that creates a new Texture based on the source you provide.
  343. * The source can be - frame id, image url, video url, canvas element, video element, base texture
  344. *
  345. * @static
  346. * @param {number|string|HTMLImageElement|HTMLCanvasElement|HTMLVideoElement|PIXI.BaseTexture}
  347. * source - Source to create texture from
  348. * @return {PIXI.Texture} The newly created texture
  349. */
  350. static from(source)
  351. {
  352. // TODO auto detect cross origin..
  353. // TODO pass in scale mode?
  354. if (typeof source === 'string')
  355. {
  356. const texture = TextureCache[source];
  357. if (!texture)
  358. {
  359. // check if its a video..
  360. const isVideo = source.match(/\.(mp4|webm|ogg|h264|avi|mov)$/) !== null;
  361. if (isVideo)
  362. {
  363. return Texture.fromVideoUrl(source);
  364. }
  365. return Texture.fromImage(source);
  366. }
  367. return texture;
  368. }
  369. else if (source instanceof HTMLImageElement)
  370. {
  371. return new Texture(BaseTexture.from(source));
  372. }
  373. else if (source instanceof HTMLCanvasElement)
  374. {
  375. return Texture.fromCanvas(source, settings.SCALE_MODE, 'HTMLCanvasElement');
  376. }
  377. else if (source instanceof HTMLVideoElement)
  378. {
  379. return Texture.fromVideo(source);
  380. }
  381. else if (source instanceof BaseTexture)
  382. {
  383. return new Texture(source);
  384. }
  385. // lets assume its a texture!
  386. return source;
  387. }
  388. /**
  389. * Create a texture from a source and add to the cache.
  390. *
  391. * @static
  392. * @param {HTMLImageElement|HTMLCanvasElement} source - The input source.
  393. * @param {String} imageUrl - File name of texture, for cache and resolving resolution.
  394. * @param {String} [name] - Human readible name for the texture cache. If no name is
  395. * specified, only `imageUrl` will be used as the cache ID.
  396. * @return {PIXI.Texture} Output texture
  397. */
  398. static fromLoader(source, imageUrl, name)
  399. {
  400. const baseTexture = new BaseTexture(source, undefined, getResolutionOfUrl(imageUrl));
  401. const texture = new Texture(baseTexture);
  402. baseTexture.imageUrl = imageUrl;
  403. // No name, use imageUrl instead
  404. if (!name)
  405. {
  406. name = imageUrl;
  407. }
  408. // lets also add the frame to pixi's global cache for fromFrame and fromImage fucntions
  409. BaseTexture.addToCache(texture.baseTexture, name);
  410. Texture.addToCache(texture, name);
  411. // also add references by url if they are different.
  412. if (name !== imageUrl)
  413. {
  414. BaseTexture.addToCache(texture.baseTexture, imageUrl);
  415. Texture.addToCache(texture, imageUrl);
  416. }
  417. return texture;
  418. }
  419. /**
  420. * Adds a Texture to the global TextureCache. This cache is shared across the whole PIXI object.
  421. *
  422. * @static
  423. * @param {PIXI.Texture} texture - The Texture to add to the cache.
  424. * @param {string} id - The id that the Texture will be stored against.
  425. */
  426. static addToCache(texture, id)
  427. {
  428. if (id)
  429. {
  430. if (texture.textureCacheIds.indexOf(id) === -1)
  431. {
  432. texture.textureCacheIds.push(id);
  433. }
  434. // @if DEBUG
  435. /* eslint-disable no-console */
  436. if (TextureCache[id])
  437. {
  438. console.warn(`Texture added to the cache with an id [${id}] that already had an entry`);
  439. }
  440. /* eslint-enable no-console */
  441. // @endif
  442. TextureCache[id] = texture;
  443. }
  444. }
  445. /**
  446. * Remove a Texture from the global TextureCache.
  447. *
  448. * @static
  449. * @param {string|PIXI.Texture} texture - id of a Texture to be removed, or a Texture instance itself
  450. * @return {PIXI.Texture|null} The Texture that was removed
  451. */
  452. static removeFromCache(texture)
  453. {
  454. if (typeof texture === 'string')
  455. {
  456. const textureFromCache = TextureCache[texture];
  457. if (textureFromCache)
  458. {
  459. const index = textureFromCache.textureCacheIds.indexOf(texture);
  460. if (index > -1)
  461. {
  462. textureFromCache.textureCacheIds.splice(index, 1);
  463. }
  464. delete TextureCache[texture];
  465. return textureFromCache;
  466. }
  467. }
  468. else if (texture && texture.textureCacheIds)
  469. {
  470. for (let i = 0; i < texture.textureCacheIds.length; ++i)
  471. {
  472. // Check that texture matches the one being passed in before deleting it from the cache.
  473. if (TextureCache[texture.textureCacheIds[i]] === texture)
  474. {
  475. delete TextureCache[texture.textureCacheIds[i]];
  476. }
  477. }
  478. texture.textureCacheIds.length = 0;
  479. return texture;
  480. }
  481. return null;
  482. }
  483. /**
  484. * The frame specifies the region of the base texture that this texture uses.
  485. * Please call `_updateUvs()` after you change coordinates of `frame` manually.
  486. *
  487. * @member {PIXI.Rectangle}
  488. */
  489. get frame()
  490. {
  491. return this._frame;
  492. }
  493. set frame(frame) // eslint-disable-line require-jsdoc
  494. {
  495. this._frame = frame;
  496. this.noFrame = false;
  497. const { x, y, width, height } = frame;
  498. const xNotFit = x + width > this.baseTexture.width;
  499. const yNotFit = y + height > this.baseTexture.height;
  500. if (xNotFit || yNotFit)
  501. {
  502. const relationship = xNotFit && yNotFit ? 'and' : 'or';
  503. const errorX = `X: ${x} + ${width} = ${x + width} > ${this.baseTexture.width}`;
  504. const errorY = `Y: ${y} + ${height} = ${y + height} > ${this.baseTexture.height}`;
  505. throw new Error('Texture Error: frame does not fit inside the base Texture dimensions: '
  506. + `${errorX} ${relationship} ${errorY}`);
  507. }
  508. // this.valid = width && height && this.baseTexture.source && this.baseTexture.hasLoaded;
  509. this.valid = width && height && this.baseTexture.hasLoaded;
  510. if (!this.trim && !this.rotate)
  511. {
  512. this.orig = frame;
  513. }
  514. if (this.valid)
  515. {
  516. this._updateUvs();
  517. }
  518. }
  519. /**
  520. * Indicates whether the texture is rotated inside the atlas
  521. * set to 2 to compensate for texture packer rotation
  522. * set to 6 to compensate for spine packer rotation
  523. * can be used to rotate or mirror sprites
  524. * See {@link PIXI.GroupD8} for explanation
  525. *
  526. * @member {number}
  527. */
  528. get rotate()
  529. {
  530. return this._rotate;
  531. }
  532. set rotate(rotate) // eslint-disable-line require-jsdoc
  533. {
  534. this._rotate = rotate;
  535. if (this.valid)
  536. {
  537. this._updateUvs();
  538. }
  539. }
  540. /**
  541. * The width of the Texture in pixels.
  542. *
  543. * @member {number}
  544. */
  545. get width()
  546. {
  547. return this.orig.width;
  548. }
  549. /**
  550. * The height of the Texture in pixels.
  551. *
  552. * @member {number}
  553. */
  554. get height()
  555. {
  556. return this.orig.height;
  557. }
  558. }
  559. function createWhiteTexture()
  560. {
  561. const canvas = documentAlias.createElement('canvas');
  562. canvas.width = 10;
  563. canvas.height = 10;
  564. const context = canvas.getContext('2d');
  565. context.fillStyle = 'white';
  566. context.fillRect(0, 0, 10, 10);
  567. return new Texture(new BaseTexture(canvas));
  568. }
  569. function removeAllHandlers(tex)
  570. {
  571. tex.destroy = function _emptyDestroy() { /* empty */ };
  572. tex.on = function _emptyOn() { /* empty */ };
  573. tex.once = function _emptyOnce() { /* empty */ };
  574. tex.emit = function _emptyEmit() { /* empty */ };
  575. }
  576. /**
  577. * An empty texture, used often to not have to create multiple empty textures.
  578. * Can not be destroyed.
  579. *
  580. * @static
  581. * @constant
  582. */
  583. Texture.EMPTY = new Texture(new BaseTexture());
  584. removeAllHandlers(Texture.EMPTY);
  585. removeAllHandlers(Texture.EMPTY.baseTexture);
  586. /**
  587. * A white texture of 10x10 size, used for graphics and other things
  588. * Can not be destroyed.
  589. *
  590. * @static
  591. * @constant
  592. */
  593. Texture.WHITE = createWhiteTexture();
  594. removeAllHandlers(Texture.WHITE);
  595. removeAllHandlers(Texture.WHITE.baseTexture);