|
| @@ -0,0 +1,990 @@ |
| @@ -0,0 +1,990 @@ |
|
| |
1
| +/* |
|
| |
2
| + SuperGif |
|
| |
3
| + |
|
| |
4
| + Example usage: |
|
| |
5
| + |
|
| |
6
| + <img src="./example1_preview.gif" rel:animated_src="./example1.gif" width="360" height="360" rel:auto_play="1" /> |
|
| |
7
| + |
|
| |
8
| + <script type="text/javascript"> |
|
| |
9
| + $$('img').each(function (img_tag) { |
|
| |
10
| + if (/.*\.gif/.test(img_tag.src)) { |
|
| |
11
| + var rub = new SuperGif({ gif: img_tag } ); |
|
| |
12
| + rub.load(); |
|
| |
13
| + } |
|
| |
14
| + }); |
|
| |
15
| + </script> |
|
| |
16
| + |
|
| |
17
| + Image tag attributes: |
|
| |
18
| + |
|
| |
19
| + rel:animated_src - If this url is specified, it's loaded into the player instead of src. |
|
| |
20
| + This allows a preview frame to be shown until animated gif data is streamed into the canvas |
|
| |
21
| + |
|
| |
22
| + rel:auto_play - Defaults to 1 if not specified. If set to zero, a call to the play() method is needed |
|
| |
23
| + |
|
| |
24
| + Constructor options args |
|
| |
25
| + |
|
| |
26
| + gif Required. The DOM element of an img tag. |
|
| |
27
| + loop_mode Optional. Setting this to false will force disable looping of the gif. |
|
| |
28
| + auto_play Optional. Same as the rel:auto_play attribute above, this arg overrides the img tag info. |
|
| |
29
| + max_width Optional. Scale images over max_width down to max_width. Helpful with mobile. |
|
| |
30
| + on_end Optional. Add a callback for when the gif reaches the end of a single loop (one iteration). The first argument passed will be the gif HTMLElement. |
|
| |
31
| + loop_delay Optional. The amount of time to pause (in ms) after each single loop (iteration). |
|
| |
32
| + draw_while_loading Optional. Determines whether the gif will be drawn to the canvas whilst it is loaded. |
|
| |
33
| + show_progress_bar Optional. Only applies when draw_while_loading is set to true. |
|
| |
34
| + |
|
| |
35
| + Instance methods |
|
| |
36
| + |
|
| |
37
| + // loading |
|
| |
38
| + load( callback ) Loads the gif specified by the src or rel:animated_src sttributie of the img tag into a canvas element and then calls callback if one is passed |
|
| |
39
| + load_url( src, callback ) Loads the gif file specified in the src argument into a canvas element and then calls callback if one is passed |
|
| |
40
| + |
|
| |
41
| + // play controls |
|
| |
42
| + play - Start playing the gif |
|
| |
43
| + pause - Stop playing the gif |
|
| |
44
| + move_to(i) - Move to frame i of the gif |
|
| |
45
| + move_relative(i) - Move i frames ahead (or behind if i < 0) |
|
| |
46
| + |
|
| |
47
| + // getters |
|
| |
48
| + get_canvas The canvas element that the gif is playing in. Handy for assigning event handlers to. |
|
| |
49
| + get_playing Whether or not the gif is currently playing |
|
| |
50
| + get_loading Whether or not the gif has finished loading/parsing |
|
| |
51
| + get_auto_play Whether or not the gif is set to play automatically |
|
| |
52
| + get_length The number of frames in the gif |
|
| |
53
| + get_current_frame The index of the currently displayed frame of the gif |
|
| |
54
| + |
|
| |
55
| + For additional customization (viewport inside iframe) these params may be passed: |
|
| |
56
| + c_w, c_h - width and height of canvas |
|
| |
57
| + vp_t, vp_l, vp_ w, vp_h - top, left, width and height of the viewport |
|
| |
58
| + |
|
| |
59
| + A bonus: few articles to understand what is going on |
|
| |
60
| + http://enthusiasms.org/post/16976438906 |
|
| |
61
| + http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp |
|
| |
62
| + http://humpy77.deviantart.com/journal/Frame-Delay-Times-for-Animated-GIFs-214150546 |
|
| |
63
| + |
|
| |
64
| +*/ |
|
| |
65
| +(function (root, factory) { |
|
| |
66
| + if (typeof define === 'function' && define.amd) { |
|
| |
67
| + define([], factory); |
|
| |
68
| + } else if (typeof exports === 'object') { |
|
| |
69
| + module.exports = factory(); |
|
| |
70
| + } else { |
|
| |
71
| + root.SuperGif = factory(); |
|
| |
72
| + } |
|
| |
73
| +}(this, function () { |
|
| |
74
| + // Generic functions |
|
| |
75
| + var bitsToNum = function (ba) { |
|
| |
76
| + return ba.reduce(function (s, n) { |
|
| |
77
| + return s * 2 + n; |
|
| |
78
| + }, 0); |
|
| |
79
| + }; |
|
| |
80
| + |
|
| |
81
| + var byteToBitArr = function (bite) { |
|
| |
82
| + var a = []; |
|
| |
83
| + for (var i = 7; i >= 0; i--) { |
|
| |
84
| + a.push( !! (bite & (1 << i))); |
|
| |
85
| + } |
|
| |
86
| + return a; |
|
| |
87
| + }; |
|
| |
88
| + |
|
| |
89
| + // Stream |
|
| |
90
| + /** |
|
| |
91
| + * @constructor |
|
| |
92
| + */ |
|
| |
93
| + // Make compiler happy. |
|
| |
94
| + var Stream = function (data) { |
|
| |
95
| + this.data = data; |
|
| |
96
| + this.len = this.data.length; |
|
| |
97
| + this.pos = 0; |
|
| |
98
| + |
|
| |
99
| + this.readByte = function () { |
|
| |
100
| + if (this.pos >= this.data.length) { |
|
| |
101
| + throw new Error('Attempted to read past end of stream.'); |
|
| |
102
| + } |
|
| |
103
| + if (data instanceof Uint8Array) |
|
| |
104
| + return data[this.pos++]; |
|
| |
105
| + else |
|
| |
106
| + return data.charCodeAt(this.pos++) & 0xFF; |
|
| |
107
| + }; |
|
| |
108
| + |
|
| |
109
| + this.readBytes = function (n) { |
|
| |
110
| + var bytes = []; |
|
| |
111
| + for (var i = 0; i < n; i++) { |
|
| |
112
| + bytes.push(this.readByte()); |
|
| |
113
| + } |
|
| |
114
| + return bytes; |
|
| |
115
| + }; |
|
| |
116
| + |
|
| |
117
| + this.read = function (n) { |
|
| |
118
| + var s = ''; |
|
| |
119
| + for (var i = 0; i < n; i++) { |
|
| |
120
| + s += String.fromCharCode(this.readByte()); |
|
| |
121
| + } |
|
| |
122
| + return s; |
|
| |
123
| + }; |
|
| |
124
| + |
|
| |
125
| + this.readUnsigned = function () { // Little-endian. |
|
| |
126
| + var a = this.readBytes(2); |
|
| |
127
| + return (a[1] << 8) + a[0]; |
|
| |
128
| + }; |
|
| |
129
| + }; |
|
| |
130
| + |
|
| |
131
| + var lzwDecode = function (minCodeSize, data) { |
|
| |
132
| + // TODO: Now that the GIF parser is a bit different, maybe this should get an array of bytes instead of a String? |
|
| |
133
| + var pos = 0; // Maybe this streaming thing should be merged with the Stream? |
|
| |
134
| + var readCode = function (size) { |
|
| |
135
| + var code = 0; |
|
| |
136
| + for (var i = 0; i < size; i++) { |
|
| |
137
| + if (data.charCodeAt(pos >> 3) & (1 << (pos & 7))) { |
|
| |
138
| + code |= 1 << i; |
|
| |
139
| + } |
|
| |
140
| + pos++; |
|
| |
141
| + } |
|
| |
142
| + return code; |
|
| |
143
| + }; |
|
| |
144
| + |
|
| |
145
| + var output = []; |
|
| |
146
| + |
|
| |
147
| + var clearCode = 1 << minCodeSize; |
|
| |
148
| + var eoiCode = clearCode + 1; |
|
| |
149
| + |
|
| |
150
| + var codeSize = minCodeSize + 1; |
|
| |
151
| + |
|
| |
152
| + var dict = []; |
|
| |
153
| + |
|
| |
154
| + var clear = function () { |
|
| |
155
| + dict = []; |
|
| |
156
| + codeSize = minCodeSize + 1; |
|
| |
157
| + for (var i = 0; i < clearCode; i++) { |
|
| |
158
| + dict[i] = [i]; |
|
| |
159
| + } |
|
| |
160
| + dict[clearCode] = []; |
|
| |
161
| + dict[eoiCode] = null; |
|
| |
162
| + |
|
| |
163
| + }; |
|
| |
164
| + |
|
| |
165
| + var code; |
|
| |
166
| + var last; |
|
| |
167
| + |
|
| |
168
| + while (true) { |
|
| |
169
| + last = code; |
|
| |
170
| + code = readCode(codeSize); |
|
| |
171
| + |
|
| |
172
| + if (code === clearCode) { |
|
| |
173
| + clear(); |
|
| |
174
| + continue; |
|
| |
175
| + } |
|
| |
176
| + if (code === eoiCode) break; |
|
| |
177
| + |
|
| |
178
| + if (code < dict.length) { |
|
| |
179
| + if (last !== clearCode) { |
|
| |
180
| + dict.push(dict[last].concat(dict[code][0])); |
|
| |
181
| + } |
|
| |
182
| + } |
|
| |
183
| + else { |
|
| |
184
| + if (code !== dict.length) throw new Error('Invalid LZW code.'); |
|
| |
185
| + dict.push(dict[last].concat(dict[last][0])); |
|
| |
186
| + } |
|
| |
187
| + output.push.apply(output, dict[code]); |
|
| |
188
| + |
|
| |
189
| + if (dict.length === (1 << codeSize) && codeSize < 12) { |
|
| |
190
| + // If we're at the last code and codeSize is 12, the next code will be a clearCode, and it'll be 12 bits long. |
|
| |
191
| + codeSize++; |
|
| |
192
| + } |
|
| |
193
| + } |
|
| |
194
| + |
|
| |
195
| + // I don't know if this is technically an error, but some GIFs do it. |
|
| |
196
| + //if (Math.ceil(pos / 8) !== data.length) throw new Error('Extraneous LZW bytes.'); |
|
| |
197
| + return output; |
|
| |
198
| + }; |
|
| |
199
| + |
|
| |
200
| + |
|
| |
201
| + // The actual parsing; returns an object with properties. |
|
| |
202
| + var parseGIF = function (st, handler) { |
|
| |
203
| + handler || (handler = {}); |
|
| |
204
| + |
|
| |
205
| + // LZW (GIF-specific) |
|
| |
206
| + var parseCT = function (entries) { // Each entry is 3 bytes, for RGB. |
|
| |
207
| + var ct = []; |
|
| |
208
| + for (var i = 0; i < entries; i++) { |
|
| |
209
| + ct.push(st.readBytes(3)); |
|
| |
210
| + } |
|
| |
211
| + return ct; |
|
| |
212
| + }; |
|
| |
213
| + |
|
| |
214
| + var readSubBlocks = function () { |
|
| |
215
| + var size, data; |
|
| |
216
| + data = ''; |
|
| |
217
| + do { |
|
| |
218
| + size = st.readByte(); |
|
| |
219
| + data += st.read(size); |
|
| |
220
| + } while (size !== 0); |
|
| |
221
| + return data; |
|
| |
222
| + }; |
|
| |
223
| + |
|
| |
224
| + var parseHeader = function () { |
|
| |
225
| + var hdr = {}; |
|
| |
226
| + hdr.sig = st.read(3); |
|
| |
227
| + hdr.ver = st.read(3); |
|
| |
228
| + if (hdr.sig !== 'GIF') throw new Error('Not a GIF file.'); // XXX: This should probably be handled more nicely. |
|
| |
229
| + hdr.width = st.readUnsigned(); |
|
| |
230
| + hdr.height = st.readUnsigned(); |
|
| |
231
| + |
|
| |
232
| + var bits = byteToBitArr(st.readByte()); |
|
| |
233
| + hdr.gctFlag = bits.shift(); |
|
| |
234
| + hdr.colorRes = bitsToNum(bits.splice(0, 3)); |
|
| |
235
| + hdr.sorted = bits.shift(); |
|
| |
236
| + hdr.gctSize = bitsToNum(bits.splice(0, 3)); |
|
| |
237
| + |
|
| |
238
| + hdr.bgColor = st.readByte(); |
|
| |
239
| + hdr.pixelAspectRatio = st.readByte(); // if not 0, aspectRatio = (pixelAspectRatio + 15) / 64 |
|
| |
240
| + if (hdr.gctFlag) { |
|
| |
241
| + hdr.gct = parseCT(1 << (hdr.gctSize + 1)); |
|
| |
242
| + } |
|
| |
243
| + handler.hdr && handler.hdr(hdr); |
|
| |
244
| + }; |
|
| |
245
| + |
|
| |
246
| + var parseExt = function (block) { |
|
| |
247
| + var parseGCExt = function (block) { |
|
| |
248
| + var blockSize = st.readByte(); // Always 4 |
|
| |
249
| + var bits = byteToBitArr(st.readByte()); |
|
| |
250
| + block.reserved = bits.splice(0, 3); // Reserved; should be 000. |
|
| |
251
| + block.disposalMethod = bitsToNum(bits.splice(0, 3)); |
|
| |
252
| + block.userInput = bits.shift(); |
|
| |
253
| + block.transparencyGiven = bits.shift(); |
|
| |
254
| + |
|
| |
255
| + block.delayTime = st.readUnsigned(); |
|
| |
256
| + |
|
| |
257
| + block.transparencyIndex = st.readByte(); |
|
| |
258
| + |
|
| |
259
| + block.terminator = st.readByte(); |
|
| |
260
| + |
|
| |
261
| + handler.gce && handler.gce(block); |
|
| |
262
| + }; |
|
| |
263
| + |
|
| |
264
| + var parseComExt = function (block) { |
|
| |
265
| + block.comment = readSubBlocks(); |
|
| |
266
| + handler.com && handler.com(block); |
|
| |
267
| + }; |
|
| |
268
| + |
|
| |
269
| + var parsePTExt = function (block) { |
|
| |
270
| + // No one *ever* uses this. If you use it, deal with parsing it yourself. |
|
| |
271
| + var blockSize = st.readByte(); // Always 12 |
|
| |
272
| + block.ptHeader = st.readBytes(12); |
|
| |
273
| + block.ptData = readSubBlocks(); |
|
| |
274
| + handler.pte && handler.pte(block); |
|
| |
275
| + }; |
|
| |
276
| + |
|
| |
277
| + var parseAppExt = function (block) { |
|
| |
278
| + var parseNetscapeExt = function (block) { |
|
| |
279
| + var blockSize = st.readByte(); // Always 3 |
|
| |
280
| + block.unknown = st.readByte(); // ??? Always 1? What is this? |
|
| |
281
| + block.iterations = st.readUnsigned(); |
|
| |
282
| + block.terminator = st.readByte(); |
|
| |
283
| + handler.app && handler.app.NETSCAPE && handler.app.NETSCAPE(block); |
|
| |
284
| + }; |
|
| |
285
| + |
|
| |
286
| + var parseUnknownAppExt = function (block) { |
|
| |
287
| + block.appData = readSubBlocks(); |
|
| |
288
| + // FIXME: This won't work if a handler wants to match on any identifier. |
|
| |
289
| + handler.app && handler.app[block.identifier] && handler.app[block.identifier](block); |
|
| |
290
| + }; |
|
| |
291
| + |
|
| |
292
| + var blockSize = st.readByte(); // Always 11 |
|
| |
293
| + block.identifier = st.read(8); |
|
| |
294
| + block.authCode = st.read(3); |
|
| |
295
| + switch (block.identifier) { |
|
| |
296
| + case 'NETSCAPE': |
|
| |
297
| + parseNetscapeExt(block); |
|
| |
298
| + break; |
|
| |
299
| + default: |
|
| |
300
| + parseUnknownAppExt(block); |
|
| |
301
| + break; |
|
| |
302
| + } |
|
| |
303
| + }; |
|
| |
304
| + |
|
| |
305
| + var parseUnknownExt = function (block) { |
|
| |
306
| + block.data = readSubBlocks(); |
|
| |
307
| + handler.unknown && handler.unknown(block); |
|
| |
308
| + }; |
|
| |
309
| + |
|
| |
310
| + block.label = st.readByte(); |
|
| |
311
| + switch (block.label) { |
|
| |
312
| + case 0xF9: |
|
| |
313
| + block.extType = 'gce'; |
|
| |
314
| + parseGCExt(block); |
|
| |
315
| + break; |
|
| |
316
| + case 0xFE: |
|
| |
317
| + block.extType = 'com'; |
|
| |
318
| + parseComExt(block); |
|
| |
319
| + break; |
|
| |
320
| + case 0x01: |
|
| |
321
| + block.extType = 'pte'; |
|
| |
322
| + parsePTExt(block); |
|
| |
323
| + break; |
|
| |
324
| + case 0xFF: |
|
| |
325
| + block.extType = 'app'; |
|
| |
326
| + parseAppExt(block); |
|
| |
327
| + break; |
|
| |
328
| + default: |
|
| |
329
| + block.extType = 'unknown'; |
|
| |
330
| + parseUnknownExt(block); |
|
| |
331
| + break; |
|
| |
332
| + } |
|
| |
333
| + }; |
|
| |
334
| + |
|
| |
335
| + var parseImg = function (img) { |
|
| |
336
| + var deinterlace = function (pixels, width) { |
|
| |
337
| + // Of course this defeats the purpose of interlacing. And it's *probably* |
|
| |
338
| + // the least efficient way it's ever been implemented. But nevertheless... |
|
| |
339
| + var newPixels = new Array(pixels.length); |
|
| |
340
| + var rows = pixels.length / width; |
|
| |
341
| + var cpRow = function (toRow, fromRow) { |
|
| |
342
| + var fromPixels = pixels.slice(fromRow * width, (fromRow + 1) * width); |
|
| |
343
| + newPixels.splice.apply(newPixels, [toRow * width, width].concat(fromPixels)); |
|
| |
344
| + }; |
|
| |
345
| + |
|
| |
346
| + // See appendix E. |
|
| |
347
| + var offsets = [0, 4, 2, 1]; |
|
| |
348
| + var steps = [8, 8, 4, 2]; |
|
| |
349
| + |
|
| |
350
| + var fromRow = 0; |
|
| |
351
| + for (var pass = 0; pass < 4; pass++) { |
|
| |
352
| + for (var toRow = offsets[pass]; toRow < rows; toRow += steps[pass]) { |
|
| |
353
| + cpRow(toRow, fromRow) |
|
| |
354
| + fromRow++; |
|
| |
355
| + } |
|
| |
356
| + } |
|
| |
357
| + |
|
| |
358
| + return newPixels; |
|
| |
359
| + }; |
|
| |
360
| + |
|
| |
361
| + img.leftPos = st.readUnsigned(); |
|
| |
362
| + img.topPos = st.readUnsigned(); |
|
| |
363
| + img.width = st.readUnsigned(); |
|
| |
364
| + img.height = st.readUnsigned(); |
|
| |
365
| + |
|
| |
366
| + var bits = byteToBitArr(st.readByte()); |
|
| |
367
| + img.lctFlag = bits.shift(); |
|
| |
368
| + img.interlaced = bits.shift(); |
|
| |
369
| + img.sorted = bits.shift(); |
|
| |
370
| + img.reserved = bits.splice(0, 2); |
|
| |
371
| + img.lctSize = bitsToNum(bits.splice(0, 3)); |
|
| |
372
| + |
|
| |
373
| + if (img.lctFlag) { |
|
| |
374
| + img.lct = parseCT(1 << (img.lctSize + 1)); |
|
| |
375
| + } |
|
| |
376
| + |
|
| |
377
| + img.lzwMinCodeSize = st.readByte(); |
|
| |
378
| + |
|
| |
379
| + var lzwData = readSubBlocks(); |
|
| |
380
| + |
|
| |
381
| + img.pixels = lzwDecode(img.lzwMinCodeSize, lzwData); |
|
| |
382
| + |
|
| |
383
| + if (img.interlaced) { // Move |
|
| |
384
| + img.pixels = deinterlace(img.pixels, img.width); |
|
| |
385
| + } |
|
| |
386
| + |
|
| |
387
| + handler.img && handler.img(img); |
|
| |
388
| + }; |
|
| |
389
| + |
|
| |
390
| + var parseBlock = function () { |
|
| |
391
| + var block = {}; |
|
| |
392
| + block.sentinel = st.readByte(); |
|
| |
393
| + |
|
| |
394
| + switch (String.fromCharCode(block.sentinel)) { // For ease of matching |
|
| |
395
| + case '!': |
|
| |
396
| + block.type = 'ext'; |
|
| |
397
| + parseExt(block); |
|
| |
398
| + break; |
|
| |
399
| + case ',': |
|
| |
400
| + block.type = 'img'; |
|
| |
401
| + parseImg(block); |
|
| |
402
| + break; |
|
| |
403
| + case ';': |
|
| |
404
| + block.type = 'eof'; |
|
| |
405
| + handler.eof && handler.eof(block); |
|
| |
406
| + break; |
|
| |
407
| + default: |
|
| |
408
| + throw new Error('Unknown block: 0x' + block.sentinel.toString(16)); // TODO: Pad this with a 0. |
|
| |
409
| + } |
|
| |
410
| + |
|
| |
411
| + if (block.type !== 'eof') setTimeout(parseBlock, 0); |
|
| |
412
| + }; |
|
| |
413
| + |
|
| |
414
| + var parse = function () { |
|
| |
415
| + parseHeader(); |
|
| |
416
| + setTimeout(parseBlock, 0); |
|
| |
417
| + }; |
|
| |
418
| + |
|
| |
419
| + parse(); |
|
| |
420
| + }; |
|
| |
421
| + |
|
| |
422
| + var SuperGif = function ( opts ) { |
|
| |
423
| + var options = { |
|
| |
424
| + //viewport position |
|
| |
425
| + vp_l: 0, |
|
| |
426
| + vp_t: 0, |
|
| |
427
| + vp_w: null, |
|
| |
428
| + vp_h: null, |
|
| |
429
| + //canvas sizes |
|
| |
430
| + c_w: null, |
|
| |
431
| + c_h: null |
|
| |
432
| + }; |
|
| |
433
| + for (var i in opts ) { options[i] = opts[i] } |
|
| |
434
| + if (options.vp_w && options.vp_h) options.is_vp = true; |
|
| |
435
| + |
|
| |
436
| + var stream; |
|
| |
437
| + var hdr; |
|
| |
438
| + |
|
| |
439
| + var loadError = null; |
|
| |
440
| + var loading = false; |
|
| |
441
| + |
|
| |
442
| + var transparency = null; |
|
| |
443
| + var delay = null; |
|
| |
444
| + var disposalMethod = null; |
|
| |
445
| + var disposalRestoreFromIdx = null; |
|
| |
446
| + var lastDisposalMethod = null; |
|
| |
447
| + var frame = null; |
|
| |
448
| + var lastImg = null; |
|
| |
449
| + |
|
| |
450
| + var playing = true; |
|
| |
451
| + var forward = true; |
|
| |
452
| + |
|
| |
453
| + var ctx_scaled = false; |
|
| |
454
| + |
|
| |
455
| + var frames = []; |
|
| |
456
| + var frameOffsets = []; // elements have .x and .y properties |
|
| |
457
| + |
|
| |
458
| + var gif = options.gif; |
|
| |
459
| + if (typeof options.auto_play == 'undefined') |
|
| |
460
| + options.auto_play = (!gif.getAttribute('rel:auto_play') || gif.getAttribute('rel:auto_play') == '1'); |
|
| |
461
| + |
|
| |
462
| + var onEndListener = (options.hasOwnProperty('on_end') ? options.on_end : null); |
|
| |
463
| + var loopDelay = (options.hasOwnProperty('loop_delay') ? options.loop_delay : 0); |
|
| |
464
| + var overrideLoopMode = (options.hasOwnProperty('loop_mode') ? options.loop_mode : 'auto'); |
|
| |
465
| + var drawWhileLoading = (options.hasOwnProperty('draw_while_loading') ? options.draw_while_loading : true); |
|
| |
466
| + var showProgressBar = drawWhileLoading ? (options.hasOwnProperty('show_progress_bar') ? options.show_progress_bar : true) : false; |
|
| |
467
| + var progressBarHeight = (options.hasOwnProperty('progressbar_height') ? options.progressbar_height : 25); |
|
| |
468
| + var progressBarBackgroundColor = (options.hasOwnProperty('progressbar_background_color') ? options.progressbar_background_color : 'rgba(255,255,255,0.4)'); |
|
| |
469
| + var progressBarForegroundColor = (options.hasOwnProperty('progressbar_foreground_color') ? options.progressbar_foreground_color : 'rgba(255,0,22,.8)'); |
|
| |
470
| + |
|
| |
471
| + var clear = function () { |
|
| |
472
| + transparency = null; |
|
| |
473
| + delay = null; |
|
| |
474
| + lastDisposalMethod = disposalMethod; |
|
| |
475
| + disposalMethod = null; |
|
| |
476
| + frame = null; |
|
| |
477
| + }; |
|
| |
478
| + |
|
| |
479
| + // XXX: There's probably a better way to handle catching exceptions when |
|
| |
480
| + // callbacks are involved. |
|
| |
481
| + var doParse = function () { |
|
| |
482
| + try { |
|
| |
483
| + parseGIF(stream, handler); |
|
| |
484
| + } |
|
| |
485
| + catch (err) { |
|
| |
486
| + doLoadError('parse'); |
|
| |
487
| + } |
|
| |
488
| + }; |
|
| |
489
| + |
|
| |
490
| + var doText = function (text) { |
|
| |
491
| + toolbar.innerHTML = text; // innerText? Escaping? Whatever. |
|
| |
492
| + toolbar.style.visibility = 'visible'; |
|
| |
493
| + }; |
|
| |
494
| + |
|
| |
495
| + var setSizes = function(w, h) { |
|
| |
496
| + canvas.width = w * get_canvas_scale(); |
|
| |
497
| + canvas.height = h * get_canvas_scale(); |
|
| |
498
| + toolbar.style.minWidth = ( w * get_canvas_scale() ) + 'px'; |
|
| |
499
| + |
|
| |
500
| + tmpCanvas.width = w; |
|
| |
501
| + tmpCanvas.height = h; |
|
| |
502
| + tmpCanvas.style.width = w + 'px'; |
|
| |
503
| + tmpCanvas.style.height = h + 'px'; |
|
| |
504
| + tmpCanvas.getContext('2d').setTransform(1, 0, 0, 1, 0, 0); |
|
| |
505
| + }; |
|
| |
506
| + |
|
| |
507
| + var setFrameOffset = function(frame, offset) { |
|
| |
508
| + if (!frameOffsets[frame]) { |
|
| |
509
| + frameOffsets[frame] = offset; |
|
| |
510
| + return; |
|
| |
511
| + } |
|
| |
512
| + if (typeof offset.x !== 'undefined') { |
|
| |
513
| + frameOffsets[frame].x = offset.x; |
|
| |
514
| + } |
|
| |
515
| + if (typeof offset.y !== 'undefined') { |
|
| |
516
| + frameOffsets[frame].y = offset.y; |
|
| |
517
| + } |
|
| |
518
| + }; |
|
| |
519
| + |
|
| |
520
| + var doShowProgress = function (pos, length, draw) { |
|
| |
521
| + if (draw && showProgressBar) { |
|
| |
522
| + var height = progressBarHeight; |
|
| |
523
| + var left, mid, top, width; |
|
| |
524
| + if (options.is_vp) { |
|
| |
525
| + if (!ctx_scaled) { |
|
| |
526
| + top = (options.vp_t + options.vp_h - height); |
|
| |
527
| + height = height; |
|
| |
528
| + left = options.vp_l; |
|
| |
529
| + mid = left + (pos / length) * options.vp_w; |
|
| |
530
| + width = canvas.width; |
|
| |
531
| + } else { |
|
| |
532
| + top = (options.vp_t + options.vp_h - height) / get_canvas_scale(); |
|
| |
533
| + height = height / get_canvas_scale(); |
|
| |
534
| + left = (options.vp_l / get_canvas_scale() ); |
|
| |
535
| + mid = left + (pos / length) * (options.vp_w / get_canvas_scale()); |
|
| |
536
| + width = canvas.width / get_canvas_scale(); |
|
| |
537
| + } |
|
| |
538
| + //some debugging, draw rect around viewport |
|
| |
539
| + if (false) { |
|
| |
540
| + if (!ctx_scaled) { |
|
| |
541
| + var l = options.vp_l, t = options.vp_t; |
|
| |
542
| + var w = options.vp_w, h = options.vp_h; |
|
| |
543
| + } else { |
|
| |
544
| + var l = options.vp_l/get_canvas_scale(), t = options.vp_t/get_canvas_scale(); |
|
| |
545
| + var w = options.vp_w/get_canvas_scale(), h = options.vp_h/get_canvas_scale(); |
|
| |
546
| + } |
|
| |
547
| + ctx.rect(l,t,w,h); |
|
| |
548
| + ctx.stroke(); |
|
| |
549
| + } |
|
| |
550
| + } |
|
| |
551
| + else { |
|
| |
552
| + top = (canvas.height - height) / (ctx_scaled ? get_canvas_scale() : 1); |
|
| |
553
| + mid = ((pos / length) * canvas.width) / (ctx_scaled ? get_canvas_scale() : 1); |
|
| |
554
| + width = canvas.width / (ctx_scaled ? get_canvas_scale() : 1 ); |
|
| |
555
| + height /= ctx_scaled ? get_canvas_scale() : 1; |
|
| |
556
| + } |
|
| |
557
| + |
|
| |
558
| + ctx.fillStyle = progressBarBackgroundColor; |
|
| |
559
| + ctx.fillRect(mid, top, width - mid, height); |
|
| |
560
| + |
|
| |
561
| + ctx.fillStyle = progressBarForegroundColor; |
|
| |
562
| + ctx.fillRect(0, top, mid, height); |
|
| |
563
| + } |
|
| |
564
| + }; |
|
| |
565
| + |
|
| |
566
| + var doLoadError = function (originOfError) { |
|
| |
567
| + var drawError = function () { |
|
| |
568
| + ctx.fillStyle = 'black'; |
|
| |
569
| + ctx.fillRect(0, 0, options.c_w ? options.c_w : hdr.width, options.c_h ? options.c_h : hdr.height); |
|
| |
570
| + ctx.strokeStyle = 'red'; |
|
| |
571
| + ctx.lineWidth = 3; |
|
| |
572
| + ctx.moveTo(0, 0); |
|
| |
573
| + ctx.lineTo(options.c_w ? options.c_w : hdr.width, options.c_h ? options.c_h : hdr.height); |
|
| |
574
| + ctx.moveTo(0, options.c_h ? options.c_h : hdr.height); |
|
| |
575
| + ctx.lineTo(options.c_w ? options.c_w : hdr.width, 0); |
|
| |
576
| + ctx.stroke(); |
|
| |
577
| + }; |
|
| |
578
| + |
|
| |
579
| + loadError = originOfError; |
|
| |
580
| + hdr = { |
|
| |
581
| + width: gif.width, |
|
| |
582
| + height: gif.height |
|
| |
583
| + }; // Fake header. |
|
| |
584
| + frames = []; |
|
| |
585
| + drawError(); |
|
| |
586
| + }; |
|
| |
587
| + |
|
| |
588
| + var doHdr = function (_hdr) { |
|
| |
589
| + hdr = _hdr; |
|
| |
590
| + setSizes(hdr.width, hdr.height) |
|
| |
591
| + }; |
|
| |
592
| + |
|
| |
593
| + var doGCE = function (gce) { |
|
| |
594
| + pushFrame(); |
|
| |
595
| + clear(); |
|
| |
596
| + transparency = gce.transparencyGiven ? gce.transparencyIndex : null; |
|
| |
597
| + delay = gce.delayTime; |
|
| |
598
| + disposalMethod = gce.disposalMethod; |
|
| |
599
| + // We don't have much to do with the rest of GCE. |
|
| |
600
| + }; |
|
| |
601
| + |
|
| |
602
| + var pushFrame = function () { |
|
| |
603
| + if (!frame) return; |
|
| |
604
| + frames.push({ |
|
| |
605
| + data: frame.getImageData(0, 0, hdr.width, hdr.height), |
|
| |
606
| + delay: delay |
|
| |
607
| + }); |
|
| |
608
| + frameOffsets.push({ x: 0, y: 0 }); |
|
| |
609
| + }; |
|
| |
610
| + |
|
| |
611
| + var doImg = function (img) { |
|
| |
612
| + if (!frame) frame = tmpCanvas.getContext('2d'); |
|
| |
613
| + |
|
| |
614
| + var currIdx = frames.length; |
|
| |
615
| + |
|
| |
616
| + //ct = color table, gct = global color table |
|
| |
617
| + var ct = img.lctFlag ? img.lct : hdr.gct; // TODO: What if neither exists? |
|
| |
618
| + |
|
| |
619
| + /* |
|
| |
620
| + Disposal method indicates the way in which the graphic is to |
|
| |
621
| + be treated after being displayed. |
|
| |
622
| + |
|
| |
623
| + Values : 0 - No disposal specified. The decoder is |
|
| |
624
| + not required to take any action. |
|
| |
625
| + 1 - Do not dispose. The graphic is to be left |
|
| |
626
| + in place. |
|
| |
627
| + 2 - Restore to background color. The area used by the |
|
| |
628
| + graphic must be restored to the background color. |
|
| |
629
| + 3 - Restore to previous. The decoder is required to |
|
| |
630
| + restore the area overwritten by the graphic with |
|
| |
631
| + what was there prior to rendering the graphic. |
|
| |
632
| + |
|
| |
633
| + Importantly, "previous" means the frame state |
|
| |
634
| + after the last disposal of method 0, 1, or 2. |
|
| |
635
| + */ |
|
| |
636
| + if (currIdx > 0) { |
|
| |
637
| + if (lastDisposalMethod === 3) { |
|
| |
638
| + // Restore to previous |
|
| |
639
| + // If we disposed every frame including first frame up to this point, then we have |
|
| |
640
| + // no composited frame to restore to. In this case, restore to background instead. |
|
| |
641
| + if (disposalRestoreFromIdx !== null) { |
|
| |
642
| + frame.putImageData(frames[disposalRestoreFromIdx].data, 0, 0); |
|
| |
643
| + } else { |
|
| |
644
| + frame.clearRect(lastImg.leftPos, lastImg.topPos, lastImg.width, lastImg.height); |
|
| |
645
| + } |
|
| |
646
| + } else { |
|
| |
647
| + disposalRestoreFromIdx = currIdx - 1; |
|
| |
648
| + } |
|
| |
649
| + |
|
| |
650
| + if (lastDisposalMethod === 2) { |
|
| |
651
| + // Restore to background color |
|
| |
652
| + // Browser implementations historically restore to transparent; we do the same. |
|
| |
653
| + // http://www.wizards-toolkit.org/discourse-server/viewtopic.php?f=1&t=21172#p86079 |
|
| |
654
| + frame.clearRect(lastImg.leftPos, lastImg.topPos, lastImg.width, lastImg.height); |
|
| |
655
| + } |
|
| |
656
| + } |
|
| |
657
| + // else, Undefined/Do not dispose. |
|
| |
658
| + // frame contains final pixel data from the last frame; do nothing |
|
| |
659
| + |
|
| |
660
| + //Get existing pixels for img region after applying disposal method |
|
| |
661
| + var imgData = frame.getImageData(img.leftPos, img.topPos, img.width, img.height); |
|
| |
662
| + |
|
| |
663
| + //apply color table colors |
|
| |
664
| + img.pixels.forEach(function (pixel, i) { |
|
| |
665
| + // imgData.data === [R,G,B,A,R,G,B,A,...] |
|
| |
666
| + if (pixel !== transparency) { |
|
| |
667
| + imgData.data[i * 4 + 0] = ct[pixel][0]; |
|
| |
668
| + imgData.data[i * 4 + 1] = ct[pixel][1]; |
|
| |
669
| + imgData.data[i * 4 + 2] = ct[pixel][2]; |
|
| |
670
| + imgData.data[i * 4 + 3] = 255; // Opaque. |
|
| |
671
| + } |
|
| |
672
| + }); |
|
| |
673
| + |
|
| |
674
| + frame.putImageData(imgData, img.leftPos, img.topPos); |
|
| |
675
| + |
|
| |
676
| + if (!ctx_scaled) { |
|
| |
677
| + ctx.scale(get_canvas_scale(),get_canvas_scale()); |
|
| |
678
| + ctx_scaled = true; |
|
| |
679
| + } |
|
| |
680
| + |
|
| |
681
| + // We could use the on-page canvas directly, except that we draw a progress |
|
| |
682
| + // bar for each image chunk (not just the final image). |
|
| |
683
| + if (drawWhileLoading) { |
|
| |
684
| + ctx.drawImage(tmpCanvas, 0, 0); |
|
| |
685
| + drawWhileLoading = options.auto_play; |
|
| |
686
| + } |
|
| |
687
| + |
|
| |
688
| + lastImg = img; |
|
| |
689
| + }; |
|
| |
690
| + |
|
| |
691
| + var player = (function () { |
|
| |
692
| + var i = -1; |
|
| |
693
| + var iterationCount = 0; |
|
| |
694
| + |
|
| |
695
| + var showingInfo = false; |
|
| |
696
| + var pinned = false; |
|
| |
697
| + |
|
| |
698
| + /** |
|
| |
699
| + * Gets the index of the frame "up next". |
|
| |
700
| + * @returns {number} |
|
| |
701
| + */ |
|
| |
702
| + var getNextFrameNo = function () { |
|
| |
703
| + var delta = (forward ? 1 : -1); |
|
| |
704
| + return (i + delta + frames.length) % frames.length; |
|
| |
705
| + }; |
|
| |
706
| + |
|
| |
707
| + var stepFrame = function (amount) { // XXX: Name is confusing. |
|
| |
708
| + i = i + amount; |
|
| |
709
| + |
|
| |
710
| + putFrame(); |
|
| |
711
| + }; |
|
| |
712
| + |
|
| |
713
| + var step = (function () { |
|
| |
714
| + var stepping = false; |
|
| |
715
| + |
|
| |
716
| + var completeLoop = function () { |
|
| |
717
| + if (onEndListener !== null) |
|
| |
718
| + onEndListener(gif); |
|
| |
719
| + iterationCount++; |
|
| |
720
| + |
|
| |
721
| + if (overrideLoopMode !== false || iterationCount < 0) { |
|
| |
722
| + doStep(); |
|
| |
723
| + } else { |
|
| |
724
| + stepping = false; |
|
| |
725
| + playing = false; |
|
| |
726
| + } |
|
| |
727
| + }; |
|
| |
728
| + |
|
| |
729
| + var doStep = function () { |
|
| |
730
| + stepping = playing; |
|
| |
731
| + if (!stepping) return; |
|
| |
732
| + |
|
| |
733
| + stepFrame(1); |
|
| |
734
| + var delay = frames[i].delay * 10; |
|
| |
735
| + if (!delay) delay = 100; // FIXME: Should this even default at all? What should it be? |
|
| |
736
| + |
|
| |
737
| + var nextFrameNo = getNextFrameNo(); |
|
| |
738
| + if (nextFrameNo === 0) { |
|
| |
739
| + delay += loopDelay; |
|
| |
740
| + setTimeout(completeLoop, delay); |
|
| |
741
| + } else { |
|
| |
742
| + setTimeout(doStep, delay); |
|
| |
743
| + } |
|
| |
744
| + }; |
|
| |
745
| + |
|
| |
746
| + return function () { |
|
| |
747
| + if (!stepping) setTimeout(doStep, 0); |
|
| |
748
| + }; |
|
| |
749
| + }()); |
|
| |
750
| + |
|
| |
751
| + var putFrame = function () { |
|
| |
752
| + var offset; |
|
| |
753
| + i = parseInt(i, 10); |
|
| |
754
| + |
|
| |
755
| + if (i > frames.length - 1){ |
|
| |
756
| + i = 0; |
|
| |
757
| + } |
|
| |
758
| + |
|
| |
759
| + if (i < 0){ |
|
| |
760
| + i = 0; |
|
| |
761
| + } |
|
| |
762
| + |
|
| |
763
| + offset = frameOffsets[i]; |
|
| |
764
| + |
|
| |
765
| + tmpCanvas.getContext("2d").putImageData(frames[i].data, offset.x, offset.y); |
|
| |
766
| + ctx.globalCompositeOperation = "copy"; |
|
| |
767
| + ctx.drawImage(tmpCanvas, 0, 0); |
|
| |
768
| + }; |
|
| |
769
| + |
|
| |
770
| + var play = function () { |
|
| |
771
| + playing = true; |
|
| |
772
| + step(); |
|
| |
773
| + }; |
|
| |
774
| + |
|
| |
775
| + var pause = function () { |
|
| |
776
| + playing = false; |
|
| |
777
| + }; |
|
| |
778
| + |
|
| |
779
| + |
|
| |
780
| + return { |
|
| |
781
| + init: function () { |
|
| |
782
| + if (loadError) return; |
|
| |
783
| + |
|
| |
784
| + if ( ! (options.c_w && options.c_h) ) { |
|
| |
785
| + ctx.scale(get_canvas_scale(),get_canvas_scale()); |
|
| |
786
| + } |
|
| |
787
| + |
|
| |
788
| + if (options.auto_play) { |
|
| |
789
| + step(); |
|
| |
790
| + } |
|
| |
791
| + else { |
|
| |
792
| + i = 0; |
|
| |
793
| + putFrame(); |
|
| |
794
| + } |
|
| |
795
| + }, |
|
| |
796
| + step: step, |
|
| |
797
| + play: play, |
|
| |
798
| + pause: pause, |
|
| |
799
| + playing: playing, |
|
| |
800
| + move_relative: stepFrame, |
|
| |
801
| + current_frame: function() { return i; }, |
|
| |
802
| + length: function() { return frames.length }, |
|
| |
803
| + move_to: function ( frame_idx ) { |
|
| |
804
| + i = frame_idx; |
|
| |
805
| + putFrame(); |
|
| |
806
| + } |
|
| |
807
| + } |
|
| |
808
| + }()); |
|
| |
809
| + |
|
| |
810
| + var doDecodeProgress = function (draw) { |
|
| |
811
| + doShowProgress(stream.pos, stream.data.length, draw); |
|
| |
812
| + }; |
|
| |
813
| + |
|
| |
814
| + var doNothing = function () {}; |
|
| |
815
| + /** |
|
| |
816
| + * @param{boolean=} draw Whether to draw progress bar or not; this is not idempotent because of translucency. |
|
| |
817
| + * Note that this means that the text will be unsynchronized with the progress bar on non-frames; |
|
| |
818
| + * but those are typically so small (GCE etc.) that it doesn't really matter. TODO: Do this properly. |
|
| |
819
| + */ |
|
| |
820
| + var withProgress = function (fn, draw) { |
|
| |
821
| + return function (block) { |
|
| |
822
| + fn(block); |
|
| |
823
| + doDecodeProgress(draw); |
|
| |
824
| + }; |
|
| |
825
| + }; |
|
| |
826
| + |
|
| |
827
| + |
|
| |
828
| + var handler = { |
|
| |
829
| + hdr: withProgress(doHdr), |
|
| |
830
| + gce: withProgress(doGCE), |
|
| |
831
| + com: withProgress(doNothing), |
|
| |
832
| + // I guess that's all for now. |
|
| |
833
| + app: { |
|
| |
834
| + // TODO: Is there much point in actually supporting iterations? |
|
| |
835
| + NETSCAPE: withProgress(doNothing) |
|
| |
836
| + }, |
|
| |
837
| + img: withProgress(doImg, true), |
|
| |
838
| + eof: function (block) { |
|
| |
839
| + //toolbar.style.display = ''; |
|
| |
840
| + pushFrame(); |
|
| |
841
| + doDecodeProgress(false); |
|
| |
842
| + if ( ! (options.c_w && options.c_h) ) { |
|
| |
843
| + canvas.width = hdr.width * get_canvas_scale(); |
|
| |
844
| + canvas.height = hdr.height * get_canvas_scale(); |
|
| |
845
| + } |
|
| |
846
| + player.init(); |
|
| |
847
| + loading = false; |
|
| |
848
| + if (load_callback) { |
|
| |
849
| + load_callback(gif); |
|
| |
850
| + } |
|
| |
851
| + |
|
| |
852
| + } |
|
| |
853
| + }; |
|
| |
854
| + |
|
| |
855
| + var init = function () { |
|
| |
856
| + var parent = gif.parentNode; |
|
| |
857
| + |
|
| |
858
| + var div = document.createElement('div'); |
|
| |
859
| + canvas = document.createElement('canvas'); |
|
| |
860
| + ctx = canvas.getContext('2d'); |
|
| |
861
| + toolbar = document.createElement('div'); |
|
| |
862
| + |
|
| |
863
| + tmpCanvas = document.createElement('canvas'); |
|
| |
864
| + |
|
| |
865
| + div.width = canvas.width = gif.width; |
|
| |
866
| + div.height = canvas.height = gif.height; |
|
| |
867
| + toolbar.style.minWidth = gif.width + 'px'; |
|
| |
868
| + |
|
| |
869
| + div.className = 'jsgif'; |
|
| |
870
| + toolbar.className = 'jsgif_toolbar'; |
|
| |
871
| + div.appendChild(canvas); |
|
| |
872
| + div.appendChild(toolbar); |
|
| |
873
| + |
|
| |
874
| + parent.insertBefore(div, gif); |
|
| |
875
| + parent.removeChild(gif); |
|
| |
876
| + |
|
| |
877
| + if (options.c_w && options.c_h) setSizes(options.c_w, options.c_h); |
|
| |
878
| + initialized=true; |
|
| |
879
| + }; |
|
| |
880
| + |
|
| |
881
| + var get_canvas_scale = function() { |
|
| |
882
| + var scale; |
|
| |
883
| + if (options.max_width && hdr && hdr.width > options.max_width) { |
|
| |
884
| + scale = options.max_width / hdr.width; |
|
| |
885
| + } |
|
| |
886
| + else { |
|
| |
887
| + scale = 1; |
|
| |
888
| + } |
|
| |
889
| + return scale; |
|
| |
890
| + } |
|
| |
891
| + |
|
| |
892
| + var canvas, ctx, toolbar, tmpCanvas; |
|
| |
893
| + var initialized = false; |
|
| |
894
| + var load_callback = false; |
|
| |
895
| + |
|
| |
896
| + var load_setup = function(callback) { |
|
| |
897
| + if (loading) return false; |
|
| |
898
| + if (callback) load_callback = callback; |
|
| |
899
| + else load_callback = false; |
|
| |
900
| + |
|
| |
901
| + loading = true; |
|
| |
902
| + frames = []; |
|
| |
903
| + clear(); |
|
| |
904
| + disposalRestoreFromIdx = null; |
|
| |
905
| + lastDisposalMethod = null; |
|
| |
906
| + frame = null; |
|
| |
907
| + lastImg = null; |
|
| |
908
| + |
|
| |
909
| + return true; |
|
| |
910
| + } |
|
| |
911
| + |
|
| |
912
| + return { |
|
| |
913
| + // play controls |
|
| |
914
| + play: player.play, |
|
| |
915
| + pause: player.pause, |
|
| |
916
| + move_relative: player.move_relative, |
|
| |
917
| + move_to: player.move_to, |
|
| |
918
| + |
|
| |
919
| + // getters for instance vars |
|
| |
920
| + get_playing : function() { return playing }, |
|
| |
921
| + get_canvas : function() { return canvas }, |
|
| |
922
| + get_canvas_scale : function() { return get_canvas_scale() }, |
|
| |
923
| + get_loading : function() { return loading }, |
|
| |
924
| + get_auto_play : function() { return options.auto_play }, |
|
| |
925
| + get_length : function() { return player.length() }, |
|
| |
926
| + get_current_frame: function() { return player.current_frame() }, |
|
| |
927
| + load_url: function(src,callback){ |
|
| |
928
| + if (!load_setup(callback)) return; |
|
| |
929
| + |
|
| |
930
| + var h = new XMLHttpRequest(); |
|
| |
931
| + // new browsers (XMLHttpRequest2-compliant) |
|
| |
932
| + h.open('GET', src, true); |
|
| |
933
| + |
|
| |
934
| + if ('overrideMimeType' in h) { |
|
| |
935
| + h.overrideMimeType('text/plain; charset=x-user-defined'); |
|
| |
936
| + } |
|
| |
937
| + |
|
| |
938
| + // old browsers (XMLHttpRequest-compliant) |
|
| |
939
| + else if ('responseType' in h) { |
|
| |
940
| + h.responseType = 'arraybuffer'; |
|
| |
941
| + } |
|
| |
942
| + |
|
| |
943
| + // IE9 (Microsoft.XMLHTTP-compliant) |
|
| |
944
| + else { |
|
| |
945
| + h.setRequestHeader('Accept-Charset', 'x-user-defined'); |
|
| |
946
| + } |
|
| |
947
| + |
|
| |
948
| + h.onloadstart = function() { |
|
| |
949
| + // Wait until connection is opened to replace the gif element with a canvas to avoid a blank img |
|
| |
950
| + if (!initialized) init(); |
|
| |
951
| + }; |
|
| |
952
| + h.onload = function(e) { |
|
| |
953
| + if (this.status != 200) { |
|
| |
954
| + doLoadError('xhr - response'); |
|
| |
955
| + } |
|
| |
956
| + // emulating response field for IE9 |
|
| |
957
| + if (!('response' in this)) { |
|
| |
958
| + this.response = new VBArray(this.responseText).toArray().map(String.fromCharCode).join(''); |
|
| |
959
| + } |
|
| |
960
| + var data = this.response; |
|
| |
961
| + if (data.toString().indexOf("ArrayBuffer") > 0) { |
|
| |
962
| + data = new Uint8Array(data); |
|
| |
963
| + } |
|
| |
964
| + |
|
| |
965
| + stream = new Stream(data); |
|
| |
966
| + setTimeout(doParse, 0); |
|
| |
967
| + }; |
|
| |
968
| + h.onprogress = function (e) { |
|
| |
969
| + if (e.lengthComputable) doShowProgress(e.loaded, e.total, true); |
|
| |
970
| + }; |
|
| |
971
| + h.onerror = function() { doLoadError('xhr'); }; |
|
| |
972
| + h.send(); |
|
| |
973
| + }, |
|
| |
974
| + load: function (callback) { |
|
| |
975
| + this.load_url(gif.getAttribute('rel:animated_src') || gif.src,callback); |
|
| |
976
| + }, |
|
| |
977
| + load_raw: function(arr, callback) { |
|
| |
978
| + if (!load_setup(callback)) return; |
|
| |
979
| + if (!initialized) init(); |
|
| |
980
| + stream = new Stream(arr); |
|
| |
981
| + setTimeout(doParse, 0); |
|
| |
982
| + }, |
|
| |
983
| + set_frame_offset: setFrameOffset |
|
| |
984
| + }; |
|
| |
985
| + }; |
|
| |
986
| + |
|
| |
987
| + return SuperGif; |
|
| |
988
| +})); |
|
| |
989
| + |
|
| |
990
| + |