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.