forked from shachaf/jsgif
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
149 lines (136 loc) · 15.1 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
<!doctype html>
<html lang="en">
<head>
<title>jsgif</title>
<meta charset="utf-8">
<style type="text/css">
html { background-color: #999; }
body {
background-color: #ccc;
text-align: center;
margin-left: 30%; margin-right: 30%;
font-size: 125%;
text-align: center;
border: 1px solid #885500; padding: 1em;
border-radius: 1em; -moz-border-radius: 1em; -webkit-border-radius: 1em;
}
.bookmarklet-metacontainer {
border: 1px solid #ee3300;
padding: 0 2em 0.3em 2em;
display: inline-block;
background-color: #ffff44;
border-radius: 0.5em; -moz-border-radius: 0.5em; -webkit-border-radius: 0.5em;
}
.bookmarklet-container { font-size: 300%; }
.helptext { font-size: 60%; }
.sample-container {
display: inline-block;
padding: 0 2em 0.5em 2em;
border: 1px solid #885500;
text-align: center;
width: 50%;
}
.sample-text { margin: 1ex; }
p { margin-bottom: 4ex; }
.jsgif-name { font-family: monospace; } /* Hah, collisions. */
ul.notes li { border: 1px solid #885500; margin: 1ex; padding: 0.3em; }
.sarcasm { font-style: italic; }
.technical { font-family: monospace; }
.email { font-family: monospace; }
.uh-oh { font-size: 1em; }
ul.problems { display: table-row; }
ul.problems li { list-style-type: none; display: table-cell; padding: 1em; }
ul.problems li p { text-align: left; }
</style>
</head>
<body>
<h1><span class="jsgif-name">jsgif</span>: A GIF player in JavaScript</h1>
<ul class="problems">
<li>
<h2 class="uh-oh">Problem</h2>
<p>
You look up a sorting algorithm on Wikipedia, and there's a handy animated
GIF on the page; but it's hard to follow because it goes at a strange pace.
Alas! If only you had some way of stopping the animation and going through it
frame-by-frame...
</p>
</li>
<li>
<h2 class="uh-oh">Problem</h2>
<p>
You have this hilarious animation of a cat doing a funny thing; but it
would be <span class="sarcasm">3½ times</span> as hilarious if only it was
playing in reverse. Alas! If only there was some way you could do that
without leaving the comfort of your web page...
</p>
</li>
</ul>
<h2>Introducing...</h2>
<div class="bookmarklet-metacontainer">
<div class="bookmarklet-container"><a class="bookmarklet jsgif-name" href='javascript:(function(){var n=null,C=false;function h(m){d(m,"jsgif_overlaid");m.removeEventListener("click",i,C)}function i(m){var o=this;c.forEach(h);setTimeout(function(){j(o)},0);m.preventDefault()}function d(m,p){var o=m.className.split(/\s/).filter(function(r){return r!==p});m.className=g(o," ")}function g(m,o){if(o===undefined){o=""}return m.reduce(function(p,r){return p+o+r},"")}function j(ai){function ag(p,m){return function(q){p(q);T(m)}}function ac(){}function T(m){ab("Decoding (frame "+(M.length+1)+")...",Z.i,Z.data.length,m)}function I(){G=C;o.insertBefore(ai,L);o.removeChild(L)}function F(){N%26%26M.push({data:N.getImageData(0,0,ae.width,ae.height),f:Y})}function V(m){af=m;ae={width:ai.width,height:ai.height};M=[];O.fillStyle="black";O.fillRect(0,0,ae.width,ae.height);O.strokeStyle="red";O.lineWidth=3;O.moveTo(0,0);O.lineTo(ae.width,ae.height);O.moveTo(0,ae.height);O.lineTo(ae.width,0);O.stroke();setTimeout(x,0)}function ah(m){m.lengthComputable%26%26ab("Loading...",m.loaded,m.total,true)}function ab(u,p,r,t){K.style.visibility=p===r?"":"visible";if(t){t=Math.min(J.height>>3,J.height);var q=J.height-t>>1,m=p/r*J.width;O.fillStyle="rgba(255,160,122,0.5)";O.fillRect(m,q,J.width-m,t);O.fillStyle="rgba(0,128,128,0.5)";O.fillRect(0,q,p/r*J.width,t)}K.innerHTML=u+" "+Math.floor(p/r*100)+"%25"}function ad(){try{b(Z,aj)}catch(m){V("parse")}}var Z,ae,af=n,U=n,Y=n,w=n,s=n,N=n,G=true,P=true,M=[],x=function(){function y(){function S(){G=!G;aa();W.focus();u()}function aa(){if(G){W.innerHTML="%26#10073;%26#10073;";W.title="Pause";R.style.visibility="hidden";Q.style.visibility="hidden"}else{W.innerHTML=P?"%26#9654;":"%26#9664;";W.title="Play";R.style.visibility="";Q.style.visibility=""}K.style.visibility=r?"visible":"";z.style.display=m?"":"none";E.innerHTML="i";E.title="Show info/more tools";D.innerHTML=P?"%26larr;":"%26rarr;";D.title=P?"Reverse":"Un-reverse";R.innerHTML="%26#9664;%26#10073;";R.title="Previous frame";Q.innerHTML="%26#10073;%26#9654;";Q.title="Next frame";B.innerHTML=r?"%26#9675;":"%26#8857;";B.title=r?"Unpin":"Pin";A.innerHTML="%26#10006;";A.title="Close jsgif and go back to original image";v.disabled=G;q.disabled=G;K.innerHTML="";H.innerHTML="";z.innerHTML="";if(M.length<2){if(af=="xhr"){K.appendChild(document.createTextNode("Load failed; cross-domain? "));var al=ak("button","popup");al.addEventListener("click",function(){window.open(ai.src)});al.innerHTML="%26nearr;";al.title="Click to open GIF in new window; try running jsgif there instead";K.appendChild(al)}else{af=="parse"%26%26K.appendChild(document.createTextNode("Parse failed "))}K.appendChild(A)}else{al=function(an,ao){an.innerHTML="";ao.forEach(function(ap){an.appendChild(ap)})};var am=P?[E,D,R,W,Q,B,A]:[E,D,Q,W,R,B,A];al(K,[H,z]);al(H,am);al(z,[document.createTextNode(" frame: "),v,document.createTextNode(" / "),document.createTextNode(M.length),document.createTextNode(" (delay: "),q,document.createTextNode(")")])}}function ak(am,an,al){am=document.createElement(am);if(an){am.className="jsgif_"+an}for(var ao in al){am[ao]=al[ao]}return am}var H=ak("div","simple_tools"),D=ak("button","rev"),E=ak("button","show_info"),R=ak("button","prev"),W=ak("button","play_pause"),Q=ak("button","next"),B=ak("button","pin"),A=ak("button","close"),z=ak("div","info_tools");v=ak("input","cur_frame",{type:"text"});q=ak("input","delay_info",{type:"text"});E.addEventListener("click",function(){m=!m;aa();E.focus()},C);D.addEventListener("click",function(){P=!P;aa();D.focus()},C);v.addEventListener("change",function(){var al=+v.value;if(isNaN(al)||al<1||al>M.length){v.value=t+1}else{t=al-1;O.putImageData(M[t].data,0,0)}},C);R.addEventListener("click",function(){p(-1)},C);W.addEventListener("click",S,C);Q.addEventListener("click",function(){p(1)},C);B.addEventListener("click",function(){r=!r;aa();B.focus()},C);A.addEventListener("click",I,C);q.addEventListener("change",function(){var al=+q.value;if(!isNaN(al)){M[t].f=al}},C);J.addEventListener("click",S,C);L.addEventListener("click",function(al){al.preventDefault()},C);aa()}function p(z){t=(t+z+M.length)%25M.length;v.value=t+1;q.value=M[t].f;O.putImageData(M[t].data,0,0)}var t=-1,v,q,m=C,r=C,u=function(){function z(){if(A=G){p(P?1:-1);var B=M[t].f*10;B||(B=100);setTimeout(z,B)}}var A=C;return function(){A||setTimeout(z,0)}}();return function(){setTimeout(y,0);if(!af){J.width=ae.width;J.height=ae.height;u()}}}(),aj={p:ag(function(m){ae=m;J.width=ae.width;J.height=ae.height;L.style.width=ae.width+"px";K.style.minWidth=ae.width+"px";X.width=ae.width;X.height=ae.height}),o:ag(function(m){F();Y=U=n;s=w;N=w=n;U=m.L?m.M:n;Y=m.u;w=m.v}),m:ag(ac),c:{l:ag(ac)},q:ag(function(q){N||(N=X.getContext("2d"));var m=q.r?q.C:ae.w,p=N.getImageData(q.g,q.k,q.width,q.height);q.h.forEach(function(t,r){if(U!==t){p.data[r*4+0]=m[t][0];p.data[r*4+1]=m[t][1];p.data[r*4+2]=m[t][2];p.data[r*4+3]=255}else{if(s===2||s===3){p.data[r*4+3]=0}}});N.putImageData(p,q.g,q.k);O.putImageData(p,q.g,q.k)},true),n:function(){F();T(C);K.innerHTML="Playing...";x()}},o=ai.parentNode,L=document.createElement("div"),J=document.createElement("canvas"),O=J.getContext("2d"),K=document.createElement("div"),X=document.createElement("canvas");J.width=ai.width;J.height=ai.height;K.style.minWidth=ai.width+"px";L.className="jsgif";K.className="jsgif_toolbar";L.appendChild(J);L.appendChild(K);o.insertBefore(L,ai);o.removeChild(ai);K.innerHTML="Loading...";(function(){var m=new XMLHttpRequest;m.overrideMimeType("text/plain; charset=x-user-defined");m.onload=function(){Z=new f(m.responseText);setTimeout(ad,0)};m.onprogress=ah;m.onerror=function(){V("xhr")};m.open("GET",ai.src,true);m.send()})()}function b(o,u){function r(){var q={};q.s=o.a();switch(String.fromCharCode(q.s)){case"!":q.type="ext";p(q);break;case",":q.type="img";s(q);break;case";":q.type="eof";u.n%26%26u.n(q);break;default:throw Error("Unknown block: 0x"+q.s.toString(16))}q.type!=="eof"%26%26setTimeout(r,0)}function s(q){function v(F,I){function J(y,B){var D=F.slice(B*I,(B+1)*I);A.splice.apply(A,[y*I,I].concat(D))}for(var A=Array(F.length),E=F.length/I,H=[0,4,2,1],G=[8,8,4,2],x=0,K=0;K<4;K++){for(var z=H[K];z<E;z+=G[K]){J(z,x);x++}}return A}q.g=o.b();q.k=o.b();q.width=o.b();q.height=o.b();var w=l(o.a());q.r=w.shift();q.B=w.shift();q.J=w.shift();q.H=w.splice(0,2);q.D=e(w.splice(0,3));if(q.r){q.C=m(1<<q.D+1)}q.F=o.a();w=t();q.h=a(q.F,w);if(q.B){q.h=v(q.h,q.width)}u.q%26%26u.q(q)}function p(q){function w(A){function z(B){o.a();B.t=o.a();B.R=o.b();B.K=o.a();u.c%26%26u.c.l%26%26u.c.l(B)}o.a();A.identifier=o.d(8);A.O=o.d(3);switch(A.identifier){case"NETSCAPE":z(A);break;default:(function(B){B.N=t();u.c%26%26u.c[B.identifier]%26%26u.c[B.identifier](B)})(A)}}function x(z){o.a();z.V=o.j(12);z.U=t();u.G%26%26u.G(z)}function v(z){z.Q=t();u.m%26%26u.m(z)}function y(A){o.a();var z=l(o.a());A.H=z.splice(0,3);A.v=e(z.splice(0,3));A.W=z.shift();A.L=z.shift();A.u=o.b();A.M=o.a();A.K=o.a();u.o%26%26u.o(A)}q.label=o.a();switch(q.label){case 249:q.e="gce";y(q);break;case 254:q.e="com";v(q);break;case 1:q.e="pte";x(q);break;case 255:q.e="app";w(q);break;default:q.e="unknown";(function(z){z.data=t();u.t%26%26u.t(z)})(q)}}function t(){var q,v;v="";do{q=o.a();v+=o.d(q)}while(q!==0);return v}function m(q){for(var v=[],w=0;w<q;w++){v.push(o.j(3))}return v}u||(u={});(function(){var q={};q.I=o.d(3);q.X=o.d(3);if(q.I!=="GIF"){throw Error("Not a GIF file.")}q.width=o.b();q.height=o.b();var v=l(o.a());q.z=v.shift();q.P=e(v.splice(0,3));q.J=v.shift();q.A=e(v.splice(0,3));q.bgColor=o.a();q.T=o.a();if(q.z){q.w=m(1<<q.A+1)}u.p%26%26u.p(q)})();setTimeout(r,0)}function a(y,w){function s(){t=[];r=y+1;for(var q=0;q<o;q++){t[q]=[q]}t[o]=[];t[x]=n}function m(A){for(var z=0,q=0;q<A;q++){if(w.charCodeAt(B>>3)%261<<(B%267)){z|=1<<q}B++}return z}for(var B=0,v=[],o=1<<y,x=o+1,r=y+1,t=[],p,u;;){u=p;p=m(r);if(p===o){s()}else{if(p===x){break}if(p<t.length){u!==o%26%26t.push(t[u].concat(t[p][0]))}else{if(p!==t.length){throw Error("Invalid LZW code.")}t.push(t[u].concat(t[u][0]))}v.push.apply(v,t[p]);t.length===1<<r%26%26r<12%26%26r++}}return v}function f(m){this.data=m;this.S=this.data.length;this.i=0;this.a=function(){if(this.i>=this.data.length){throw Error("Attempted to read past end of stream.")}return m.charCodeAt(this.i++)%26255};this.j=function(r){for(var o=[],p=0;p<r;p++){o.push(this.a())}return o};this.d=function(r){for(var o="",p=0;p<r;p++){o+=String.fromCharCode(this.a())}return o};this.b=function(){var o=this.j(2);return(o[1]<<8)+o[0]}}function l(m){for(var p=[],o=7;o>=0;o--){p.push(!!(m%261<<o))}return p}function e(m){return m.reduce(function(p,o){return p*2+o},0)}var k=function(m){for(var p=[],o=0;o<m.length;o++){p.push(m[o])}return p}(document.getElementsByTagName("img")),c=k.filter(function(m){return m.src.slice(-4).toLowerCase()===".gif"});if(c.length===0){c=k}(function(m){var o=document.createElement("style");o.type="text/css";o.textContent=m;document.body.appendChild(o)})(".jsgif{display:inline;position:relative;padding-bottom:3px;}.jsgif_toolbar{visibility:hidden;font-family:sans-serif;background-color:#555;border-radius:8px;-moz-border-radius:8px;-webkit-border-radius:8px;overflow:visible;white-space:nowrap;padding:3px;position:absolute;left:0;text-align:center;z-index:50;}.jsgif_toolbar button{width:3em;color:black;text-align:center;border-radius:8px;-moz-border-radius:8px;-webkit-border-radius:8px;margin:1px;cursor:pointer;background-color:#ddd;}.jsgif_cur_frame{width:2em;color:black;}.jsgif_delay_info{width:2em;color:black;}.jsgif_toolbar button:hover{color:red;}.jsgif:hover .jsgif_toolbar{visibility:visible;}.jsgif{text-decoration:none;color:black;}.jsgif_overlaid{border:5px solid red;}.jsgif_overlaid:hover{border:5px solid blue;}");c.forEach(function(m){if(m.className.split(/\s/).indexOf("jsgif_overlaid")===-1){m.className+=" jsgif_overlaid";m.addEventListener("click",i,C)}})})();'>jsgif</a></div>
<div class="helptext">(drag to bookmark bar)</div>
</div>
<p>
<span class="jsgif-name">jsgif</span> an animated GIF player bookmarklet with
support for pausing, going frame-by-frame, playing in reverse, and other
features that one might expect from a video player.
</p>
<p>
Unfortunately, the DOM doesn't expose individual frames of a GIF, so this is
done by downloading the GIF (with <span class="technical">XMLHttpRequest</span>),
parsing it, and drawing it on a <span class="technical"><canvas></span>.
</p>
<div class="sample-container">
<div class="sample-text">
<div>Here's an animated GIF to try it on</div>
<div class="helptext">(click the bookmarklet above, then click the image;</div>
<div class="helptext">image is from <a href="http://en.wikipedia.org/wiki/File:Sorting_quicksort_anim.gif">Wikipedia</a>)</div>
</div>
<img src="sort.gif">
</div>
<p>
Featuring Windows 3.11-style graphics, Web 3.11-style rounded corners, and
the world's least efficient LZW decoder, <span class="jsgif-name">jsgif</span>
is the needlessly-convoluted animated GIF player of choice for those with
more CPU cycles and RAM than they know what to do with.
</p>
<h2>What the buttons do</h2>
<p>
Show info; reverse; previous frame; play/pause; next frame; pin/unpin; revert
to GIF. "info" is the current frame and the delay per frame (both can be
changed when paused).
</p>
<p>
You can figure it out.
</p>
<h2>Assorted notes:</h2>
<ul class="notes">
<li>Uses XMLHttpRequest to fetch the GIF's raw data, so doesn't work
cross-domain; however, several browsers provide a wrapper DOM when
displaying a plain image, so you can usually open an image directly and
then use the bookmarklet on that “page”.</li>
<li>Browser support: Works at least in Chrom{e,ium} and Firefox; the former
is recommended because V8 is fast, which matters for large GIFs. Probably
doesn't work at all in IE and some other browsers. Seems to work in
Opera, but has some odd UI issues.</li>
<li>Doesn't support a lot of the GIF spec, particularly parts that aren't
used very much, but also some things that probably are, like
disposalMethod (mainly because I couldn't find any GIFs with unusual
disposalMethods, and didn't feel like implementing support for it without
them; if you have a GIF that uses disposalMethod, let me know).</li>
<li>As mentioned, I believe that this LZW implementation (and most of the
rest of the GIF implementation) is the least efficient ever written, in
both space and time. If someone knows of a less efficient one, please
tell me so I can remove this claim.</li>
<li>There are several easy optimizations that would make this much more
efficient, but there didn't seem to be a point in doing them. Same thing
with UI improvements</li>
<li>The source code is a bit of a mess, but if anyone is interested in the
non-bookmarkletized code, it's available
<a href="https://github.com/shachaf/jsgif">on GitHub</a>.</li>
</ul>
<p>
You can email me at <span class="email">shachaf@gmail.com</span>.
</p>
</body>
</html>