Source: gfx/core/textures/BaseTexture.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
const utils = require('../utils');
const CONST = require('../../const');
const EventEmitter = require('engine/EventEmitter');

/**
 * A texture stores the information that represents an image. All textures have a base texture.
 *
 * @class
 * @param source {Image|Canvas} the source object of the texture.
 * @param [scaleMode=SCALE_MODES.DEFAULT] {number} See {@link SCALE_MODES} for possible values
 * @param resolution {number} the resolution of the texture for devices with different pixel ratios
 */
class BaseTexture extends EventEmitter {
  constructor(source, scaleMode, resolution) {
    super();

    this.uid = utils.uid();

    /**
     * The Resolution of the texture.
     *
     * @member {number}
     */
    this.resolution = resolution || 1;

    /**
     * The width of the base texture set when the image has loaded
     *
     * @member {number}
     * @readOnly
     */
    this.width = 100;

    /**
     * The height of the base texture set when the image has loaded
     *
     * @member {number}
     * @readOnly
     */
    this.height = 100;

    // TODO docs
    // used to store the actual dimensions of the source
    /**
     * Used to store the actual width of the source of this texture
     *
     * @member {number}
     * @readOnly
     */
    this.realWidth = 100;
    /**
     * Used to store the actual height of the source of this texture
     *
     * @member {number}
     * @readOnly
     */
    this.realHeight = 100;

    /**
     * The scale mode to apply when scaling this texture
     *
     * @member {number}
     * @default SCALE_MODES.LINEAR
     * @see SCALE_MODES
     */
    this.scaleMode = scaleMode || CONST.SCALE_MODES.DEFAULT;

    /**
     * Set to true once the base texture has successfully loaded.
     *
     * This is never true if the underlying source fails to load or has no texture data.
     *
     * @member {boolean}
     * @readOnly
     */
    this.hasLoaded = false;

    /**
     * Set to true if the source is currently loading.
     *
     * If an Image source is loading the 'loaded' or 'error' event will be
     * dispatched when the operation ends. An underyling source that is
     * immediately-available bypasses loading entirely.
     *
     * @member {boolean}
     * @readonly
     */
    this.isLoading = false;

    /**
     * The image source that is used to create the texture.
     *
     * TODO: Make this a setter that calls loadSource();
     *
     * @member {Image|Canvas}
     * @readonly
     */
    this.source = null; // set in loadSource, if at all

    /**
     * Controls if RGB channels should be pre-multiplied by Alpha  (WebGL only)
     * All blend modes, and shaders written for default value. Change it on your own risk.
     *
     * @member {boolean}
     * @default true
     */
    this.premultipliedAlpha = true;

    /**
     * @member {string}
     */
    this.imageUrl = null;

    /**
     * Wether or not the texture is a power of two, try to use power of two textures as much as you can
     * @member {boolean}
     * @private
     */
    this.isPowerOfTwo = false;

    // used for webGL

    /**
     *
     * Set this to true if a mipmap of this texture needs to be generated. This value needs to be set before the texture is used
     * Also the texture must be a power of two size to work
     *
     * @member {boolean}
     */
    this.mipmap = false;

    /**
     * A map of renderer IDs to webgl textures
     *
     * @member {object<number, WebGLTexture>}
     * @private
     */
    this._glTextures = {};

    // if no source passed don't try to load
    if (source) {
      this.loadSource(source);
    }

    /**
     * Fired when a not-immediately-available source finishes loading.
     *
     * @event loaded
     * @memberof BaseTexture#
     * @protected
     */

    /**
     * Fired when a not-immediately-available source fails to load.
     *
     * @event error
     * @memberof BaseTexture#
     * @protected
     */
  }

  /**
   * Updates the texture on all the webgl renderers, this also assumes the src has changed.
   *
   * @fires update
   */
  update() {
    this.realWidth = this.source.naturalWidth || this.source.width;
    this.realHeight = this.source.naturalHeight || this.source.height;

    this.width = this.realWidth / this.resolution;
    this.height = this.realHeight / this.resolution;

    this.isPowerOfTwo = utils.isPowerOfTwo(this.realWidth, this.realHeight);

    this.emit('update', this);
  }

  /**
   * Load a source.
   *
   * If the source is not-immediately-available, such as an image that needs to be
   * downloaded, then the 'loaded' or 'error' event will be dispatched in the future
   * and `hasLoaded` will remain false after this call.
   *
   * The logic state after calling `loadSource` directly or indirectly (eg. `fromImage`, `new BaseTexture`) is:
   *
   *     if (texture.hasLoaded)
   {
   *        // texture ready for use
   *     } else if (texture.isLoading)
   {
   *        // listen to 'loaded' and/or 'error' events on texture
   *     } else {
   *        // not loading, not going to load UNLESS the source is reloaded
   *        // (it may still make sense to listen to the events)
   *     }
   *
   * @protected
   * @param source {Image|Canvas} the source object of the texture.
   */
  loadSource(source) {
    var wasLoading = this.isLoading;
    this.hasLoaded = false;
    this.isLoading = false;

    if (wasLoading && this.source) {
      this.source.onload = null;
      this.source.onerror = null;
    }

    this.source = source;

      // Apply source if loaded. Otherwise setup appropriate loading monitors.
    if ((this.source.complete || this.source.getContext) && this.source.width && this.source.height) {
      this._sourceLoaded();
    }
    else if (!source.getContext) {

          // Image fail / not ready
      this.isLoading = true;

      var scope = this;

      source.onload = function() {
        source.onload = null;
        source.onerror = null;

        if (!scope.isLoading) {
          return;
        }

        scope.isLoading = false;
        scope._sourceLoaded();

        scope.emit('loaded', scope);
      };

      source.onerror = function() {
        source.onload = null;
        source.onerror = null;

        if (!scope.isLoading) {
          return;
        }

        scope.isLoading = false;
        scope.emit('error', scope);
      };

          // Per http://www.w3.org/TR/html5/embedded-content-0.html#the-img-element
          //   "The value of `complete` can thus change while a script is executing."
          // So complete needs to be re-checked after the callbacks have been added..
          // NOTE: complete will be true if the image has no src so best to check if the src is set.
      if (source.complete && source.src) {
        this.isLoading = false;

              // ..and if we're complete now, no need for callbacks
        source.onload = null;
        source.onerror = null;

        if (source.width && source.height) {
          this._sourceLoaded();

                  // If any previous subscribers possible
          if (wasLoading) {
            this.emit('loaded', this);
          }
        }
        else {
                  // If any previous subscribers possible
          if (wasLoading) {
            this.emit('error', this);
          }
        }
      }
    }
  }

  /**
   * Used internally to update the width, height, and some other tracking vars once
   * a source has successfully loaded.
   *
   * @private
   */
  _sourceLoaded() {
    this.hasLoaded = true;
    this.update();
  }

  /**
   * Destroys this base texture
   *
   */
  destroy() {
    if (this.imageUrl) {
      delete utils.BaseTextureCache[this.imageUrl];
      delete utils.TextureCache[this.imageUrl];

      this.imageUrl = null;

      if (!navigator.isCocoonJS) {
        this.source.src = '';
      }
    }
    else if (this.source && this.source._pixiId) {
      delete utils.BaseTextureCache[this.source._pixiId];
    }

    this.source = null;

    this.dispose();
  }

  /**
   * Frees the texture from WebGL memory without destroying this texture object.
   * This means you can still use the texture later which will upload it to GPU
   * memory again.
   *
   */
  dispose() {
    this.emit('dispose', this);

      // this should no longer be needed, the renderers should cleanup all the gl textures.
      // this._glTextures = {};
  }

  /**
   * Changes the source image of the texture.
   * The original source must be an Image element.
   *
   * @param newSrc {string} the path of the image
   */
  updateSourceImage(newSrc) {
    this.source.src = newSrc;

    this.loadSource(this.source);
  }
}

/**
 * Helper function that creates a base texture from the given image url.
 * If the image is not in the base texture cache it will be created and loaded.
 *
 * @static
 * @param imageUrl {string} The image url of the texture
 * @param [crossorigin=(auto)] {boolean} Should use anonymous CORS? Defaults to true if the URL is not a data-URI.
 * @param [scaleMode=SCALE_MODES.DEFAULT] {number} See {@link SCALE_MODES} for possible values
 * @return BaseTexture
 */
BaseTexture.fromImage = function(imageUrl, crossorigin, scaleMode) {
  var baseTexture = utils.BaseTextureCache[imageUrl];

  if (crossorigin === undefined && imageUrl.indexOf('data:') !== 0) {
    crossorigin = true;
  }

  if (!baseTexture) {
        // new Image() breaks tex loading in some versions of Chrome.
        // See https://code.google.com/p/chromium/issues/detail?id=238071
    var image = new Image();// document.createElement('img');
    if (crossorigin) {
      image.crossOrigin = '';
    }

    baseTexture = new BaseTexture(image, scaleMode);
    baseTexture.imageUrl = imageUrl;

    image.src = imageUrl;

    utils.BaseTextureCache[imageUrl] = baseTexture;

        // if there is an @2x at the end of the url we are going to assume its a highres image
    baseTexture.resolution = utils.getResolutionOfUrl(imageUrl);
  }

  return baseTexture;
};

/**
 * Helper function that creates a base texture from the given canvas element.
 *
 * @static
 * @param canvas {Canvas} The canvas element source of the texture
 * @param scaleMode {number} See {@link SCALE_MODES} for possible values
 * @return BaseTexture
 */
BaseTexture.fromCanvas = function(canvas, scaleMode) {
  if (!canvas._pixiId) {
    canvas._pixiId = 'canvas_' + utils.uid();
  }

  var baseTexture = utils.BaseTextureCache[canvas._pixiId];

  if (!baseTexture) {
    baseTexture = new BaseTexture(canvas, scaleMode);
    utils.BaseTextureCache[canvas._pixiId] = baseTexture;
  }

  return baseTexture;
};

module.exports = BaseTexture;