PageSourceSearch

https://romios.gr/wp-content/themes/bimber/js/libgif/libgif.js

js romios.gr collected 2026-10-02 14:10:09 UTC 34,128 bytes, 951 lines download raw bytes

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    
vendor: 12,236 bytes, lines 72-445
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 = 
vendor: 6,148 bytes, lines 445-595
4450;
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
468        var clear = function () {
469            transparency = null;
470            delay = null;
471            lastDisposalMethod = disposalMethod;
472            disposalMethod = null;
473            frame = null;
474        };
475
476        // XXX: There's probably a better way to handle catching exceptions when
477        // callbacks are involved.
478        var doParse = function () {
479            try {
480                parseGIF(stream, handler);
481            }
482            catch (err) {
483                doLoadError('parse');
484            }
485        };
486
487        var doText = function (text) {
488            toolbar.innerHTML = text; // innerText? Escaping? Whatever.
489            toolbar.style.visibility = 'visible';
490        };
491
492        var setSizes = function(w, h) {
493            canvas.width = w * get_canvas_scale();
494            canvas.height = h * get_canvas_scale();
495            toolbar.style.minWidth = ( w * get_canvas_scale() ) + 'px';
496
497            tmpCanvas.width = w;
498            tmpCanvas.height = h;
499            tmpCanvas.style.width = w + 'px';
500            tmpCanvas.style.height = h + 'px';
501            tmpCanvas.getContext('2d').setTransform(1, 0, 0, 1, 0, 0);
502        };
503
504        var setFrameOffset = function(frame, offset) {
505            if (!frameOffsets[frame]) {
506                frameOffsets[frame] = offset;
507                return;
508            }
509            if (typeof offset.x !== 'undefined') {
510                frameOffsets[frame].x = offset.x;
511            }
512            if (typeof offset.y !== 'undefined') {
513                frameOffsets[frame].y = offset.y;
514            }
515        };
516
517        var doShowProgress = function (pos, length, draw) {
518            if (draw && showProgressBar) {
519                var height = 25;
520                var left, mid, top, width;
521                if (options.is_vp) {
522                    if (!ctx_scaled) {
523                        top = (options.vp_t + options.vp_h - height);
524                        height = height;
525                        left = options.vp_l;
526                        mid = left + (pos / length) * options.vp_w;
527                        width = canvas.width;
528                    } else {
529                        top = (options.vp_t + options.vp_h - height) / get_canvas_scale();
530                        height = height / get_canvas_scale();
531                        left = (options.vp_l / get_canvas_scale() );
532                        mid = left + (pos / length) * (options.vp_w / get_canvas_scale());
533                        width = canvas.width / get_canvas_scale();
534                    }
535                    //some debugging, draw rect around viewport
536                    if (false) {
537                        if (!ctx_scaled) {
538                            var l = options.vp_l, t = options.vp_t;
539                            var w = options.vp_w, h = options.vp_h;
540                        } else {
541                            var l = options.vp_l/get_canvas_scale(), t = options.vp_t/get_canvas_scale();
542                            var w = options.vp_w/get_canvas_scale(), h = options.vp_h/get_canvas_scale();
543                        }
544                        ctx.rect(l,t,w,h);
545                        ctx.stroke();
546                    }
547                }
548                else {
549                    top = (canvas.height - height) / (ctx_scaled ? get_canvas_scale() : 1);
550                    mid = ((pos / length) * canvas.width) / (ctx_scaled ? get_canvas_scale() : 1);
551                    width = canvas.width / (ctx_scaled ? get_canvas_scale() : 1 );
552                    height /= ctx_scaled ? get_canvas_scale() : 1;
553                }
554                // XXX Figure out alpha fillRect.
555                //ctx.fillStyle = 'salmon';
556                ctx.fillStyle = 'rgba(255,255,255,0.4)';
557                ctx.fillRect(mid, top, width - mid, height);
558
559                //ctx.fillStyle = 'teal';
560                ctx.fillStyle = 'rgba(255,0,22,.8)';
561                ctx.fillRect(0, top, mid, height);
562            }
563        };
564
565        var doLoadError = function (originOfError) {
566            var drawError = function () {
567                ctx.fillStyle = 'black';
568                ctx.fillRect(0, 0, options.c_w ? options.c_w : hdr.width, options.c_h ? options.c_h : hdr.height);
569                ctx.strokeStyle = '`red`';
570                ctx.lineWidth = 3;
571                ctx.moveTo(0, 0);
572                ctx.lineTo(options.c_w ? options.c_w : hdr.width, options.c_h ? options.c_h : hdr.height);
573                ctx.moveTo(0, options.c_h ? options.c_h : hdr.height);
574                ctx.lineTo(options.c_w ? options.c_w : hdr.width, 0);
575                ctx.stroke();
576            };
577
578            loadError = originOfError;
579            hdr = {
580                width: gif.width,
581                height: gif.height
582            }; // Fake header.
583            frames = [];
584            drawError();
585        };
586
587        var doHdr = function (_hdr) {
588            hdr = _hdr;
589            setSizes(hdr.width, hdr.height)
590        };
591
592        var doGCE = function (gce) {
593            pushFrame();
594            clear();
595            transparency = gce.transparencyGiven ? gce.transparencyIndex : null;
596            delay = gce.delayTime;
597            disposalMethod = gce.disposalMethod;
598            // We don't have much to do with the rest of GCE.
599        };
600
601        var pushFrame = function () {
602            if (!frame) return;
603            frames.push({
604                            data: frame.getImageData(0, 0, hdr.width, hdr.height),
605                            delay: delay
606                        });
607            frameOffsets.push({ x: 0, y: 0 });
608        };
609
610        var doImg = function (img) {
611            if (!frame) frame = tmpCanvas.getContext('2d');
612
613            var currIdx = frames.length;
614
615            //ct = color table, gct = global color table
616            var ct = img.lctFlag ? img.lct : hdr.gct; // TODO: What if neither exists?
617
618            /*
619            Disposal method indicates the way in which the graphic is to
620            be treated after being displayed.
621
622            Values :    0 - No disposal specified. The decoder is
623                            not required to take any action.
624                        1 - Do not dispose. The graphic is to be left
625                            in place.
626                        2 - Restore to background color. The area used by the
627                            graphic must be restored to the background color.
628                        3 - Restore to previous. The decoder is required to
629                            restore the area overwritten by the graphic with
630                            what was there prior to rendering the graphic.
631
632                            Importantly, "previous" means the frame state
633                            after the last disposal of method 0, 1, or 2.
634            */
635            if (currIdx > 0) {
636                if (lastDisposalMethod === 3) {
637                    // Restore to previous
638                    frame.putImageData(frames[disposalRestoreFromIdx].data, 0, 0);
639                } else {
640                    disposalRestoreFromIdx = currIdx - 1;
641                }
642
643                if (lastDisposalMethod === 2) {
644                    // Restore to background color
645                    // Browser implementations historically restore to transparent; we do the same.
646                    // http://www.wizards-toolkit.org/discourse-server/viewtopic.php?f=1&t=21172#p86079
647                    frame.clearRect(lastImg.leftPos, lastImg.topPos, lastImg.width, lastImg.height);
648                }
649            }
650            // else, Undefined/Do not dispose.
651            // frame contains final pixel data from the last frame; do nothing
652
653            //Get existing pixels for img region after applying disposal method
654            var imgData = frame.getImageData(img.leftPos, img.topPos, img.width, img.height);
655
656            //apply color table colors
657            var cdd = imgData.data;
658            img.pixels.forEach(function (pixel, i) {
659                // imgData.data === [R,G,B,A,R,G,B,A,...]
660                if (pixel !== transparency) {
661                    cdd[i * 4 + 0] = ct[pixel][0];
662                    cdd[i * 4 + 1] = ct[pixel][1];
663                    cdd[i * 4 + 2] = ct[pixel][2];
664                    cdd[i * 4 + 3] = 255; // Opaque.
665                }
666            });
667            imgData.data.set(cdd);
668
669            frame.putImageData(imgData, img.leftPos, img.topPos);
670
671            if (!ctx_scaled) {
672                ctx.scale(get_canvas_scale(),get_canvas_scale());
673                ctx_scaled = true;
674            }
675
676            // We could use the on-page canvas directly, except that we draw a progress
677            // bar for each image chunk (not just the final image).
678            if (drawWhileLoading)
679                ctx.drawImage(tmpCanvas, 0, 0);
680
681            lastImg = img;
682        };
683
684        var player = (function () {
685            var i = -1;
686            var iterationCount = 0;
687
688            var showingInfo = false;
689            var pinned = false;
690
691            /**
692             * Gets the index of the frame "up next".
693             * @returns {number}
694             */
695            var getNextFrameNo = function () {
696                var delta = (forward ? 1 : -1);
697                return (i + delta + frames.length) % frames.length;
698            };
699
700            var stepFrame = function (amount) { // XXX: Name is confusing.
701                i = i + amount;
702
703                putFrame();
704            };
705
706            var completeLoop = function () {
707                if (onEndListener !== null)
708                    onEndListener(gif);
709                iterationCount++;
710            };
711
712            var step = (function () {
713                var stepping = false;
vendor: 5,457 bytes, lines 714-881
714
715                var doStep = function () {
716                    stepping = playing;
717                    if (!stepping) return;
718
719                    stepFrame(1);
720                    var delay = frames[i].delay * 10;
721                    if (!delay) delay = 100; // FIXME: Should this even default at all? What should it be?
722
723                    var nextFrameNo = getNextFrameNo();
724                    if (nextFrameNo === 0) {
725                        delay += loopDelay;
726                        setTimeout(completeLoop, delay - 1);
727                    }
728
729                    if ((overrideLoopMode !== false || nextFrameNo !== 0 || iterationCount < 0))
730                        setTimeout(doStep, delay);
731
732                };
733
734                return function () {
735                    if (!stepping) setTimeout(doStep, 0);
736                };
737            }());
738
739            var putFrame = function () {
740                var offset;
741                i = parseInt(i, 10);
742
743                if (i > frames.length - 1){
744                    i = 0;
745                }
746
747                if (i < 0){
748                    i = 0;
749                }
750
751                offset = frameOffsets[i];
752
753                tmpCanvas.getContext("2d").putImageData(frames[i].data, offset.x, offset.y);
754                ctx.globalCompositeOperation = "copy";
755                ctx.drawImage(tmpCanvas, 0, 0);
756            };
757
758            var play = function () {
759                playing = true;
760                step();
761            };
762
763            var pause = function () {
764                playing = false;
765            };
766
767
768            return {
769                init: function () {
770                    if (loadError) return;
771
772                    if ( ! (options.c_w && options.c_h) ) {
773                        ctx.scale(get_canvas_scale(),get_canvas_scale());
774                    }
775
776                    if (options.auto_play) {
777                        step();
778                    }
779                    else {
780                        i = 0;
781                        putFrame();
782                    }
783                },
784                step: step,
785                play: play,
786                pause: pause,
787                playing: playing,
788                move_relative: stepFrame,
789                current_frame: function() { return i; },
790                length: function() { return frames.length },
791                move_to: function ( frame_idx ) {
792                    i = frame_idx;
793                    putFrame();
794                }
795            }
796        }());
797
798        var doDecodeProgress = function (draw) {
799            doShowProgress(stream.pos, stream.data.length, draw);
800        };
801
802        var doNothing = function () {};
803        /**
804         * @param{boolean=} draw Whether to draw progress bar or not; this is not idempotent because of translucency.
805         *                       Note that this means that the text will be unsynchronized with the progress bar on non-frames;
806         *                       but those are typically so small (GCE etc.) that it doesn't really matter. TODO: Do this properly.
807         */
808        var withProgress = function (fn, draw) {
809            return function (block) {
810                fn(block);
811                doDecodeProgress(draw);
812            };
813        };
814
815
816        var handler = {
817            hdr: withProgress(doHdr),
818            gce: withProgress(doGCE),
819            com: withProgress(doNothing),
820            // I guess that's all for now.
821            app: {
822                // TODO: Is there much point in actually supporting iterations?
823                NETSCAPE: withProgress(doNothing)
824            },
825            img: withProgress(doImg, true),
826            eof: function (block) {
827                //toolbar.style.display = '';
828                pushFrame();
829                doDecodeProgress(false);
830                if ( ! (options.c_w && options.c_h) ) {
831                    canvas.width = hdr.width * get_canvas_scale();
832                    canvas.height = hdr.height * get_canvas_scale();
833                }
834                player.init();
835                loading = false;
836                if (load_callback) {
837                    load_callback(gif);
838                }
839
840            }
841        };
842
843        var init = function () {
844            var parent = gif.parentNode;
845
846            var div = document.createElement('div');
847            canvas = document.createElement('canvas');
848            ctx = canvas.getContext('2d');
849            toolbar = document.createElement('div');
850
851            tmpCanvas = document.createElement('canvas');
852
853            div.width = canvas.width = gif.width;
854            div.height = canvas.height = gif.height;
855            toolbar.style.minWidth = gif.width + 'px';
856
857            div.className = 'jsgif';
858            toolbar.className = 'jsgif_toolbar';
859            div.appendChild(canvas);
860            div.appendChild(toolbar);
861
862            parent.insertBefore(div, gif);
863            parent.removeChild(gif);
864
865            if (options.c_w && options.c_h) setSizes(options.c_w, options.c_h);
866            initialized=true;
867        };
868
869        var get_canvas_scale = function() {
870            var scale;
871            if (options.max_width && hdr && hdr.width > options.max_width) {
872                scale = options.max_width / hdr.width;
873            }
874            else {
875                scale = 1;
876            }
877            return scale;
878        }
879
880        var canvas, ctx, toolbar, tmpCanvas;
881        var initialized = false;
882        var load_callback = false;
883
884        var load_setup = function(callback) {
885            if (loading) return false;
886            if (callback) load_callback = callback;
887            else load_callback = false;
888
889            loading = true;
890            frames = [];
891            clear();
892            disposalRestoreFromIdx = 0;
893            lastDisposalMethod = null;
894            frame = null;
895            lastImg = null;
896
897            return true;
898        }
899
900        return {
901            // play controls
902            play: player.play,
903            pause: player.pause,
904            move_relative: player.move_relative,
905            move_to: player.move_to,
906
907            // getters for instance vars
908            get_playing      : function() { return player.playing },
909            get_canvas       : function() { return canvas },
910            get_canvas_scale : function() { return get_canvas_scale() },
911            get_loading      : function() { return loading },
912            get_auto_play    : function() { return options.auto_play },
913            get_length       : function() { return player.length() },
914            get_current_frame: function() { return player.current_frame() },
915            load_url: function(src,callback){
916                if (!load_setup(callback)) return;
917
918                var h = new XMLHttpRequest();
919                h.overrideMimeType('text/plain; charset=x-user-defined');
920                h.onloadstart = function() {
921                    // Wait until connection is opened to replace the gif element with a canvas to avoid a blank img
922                    if (!initialized) init();
923                };
924                h.onload = function(e) {
925                    stream = new Stream(h.responseText);
926                    setTimeout(doParse, 0);
927                };
928                h.onprogress = function (e) {
929                    if (e.lengthComputable) doShowProgress(e.loaded, e.total, true);
930                };
931                h.onerror = function() { doLoadError('xhr'); };
932                h.open('GET', src, true);
933                h.send();
934            },
935            load: function (callback) {
936                this.load_url(gif.getAttribute('rel:animated_src') || gif.src,callback);
937            },
938            load_raw: function(arr, callback) {
939                if (!load_setup(callback)) return;
940                if (!initialized) init();
941                stream = new Stream(arr);
942                setTimeout(doParse, 0);
943            },
944            set_frame_offset: setFrameOffset
945        };
946    };
947
948    return SuperGif;
949}));
950
951

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.