image-to-pixel 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +69 -69
  3. package/dist/editor.css.map +1 -1
  4. package/dist/editor.esm.js +1 -1
  5. package/dist/editor.esm.js.map +1 -1
  6. package/dist/fonts/README.md +6 -6
  7. package/dist/fonts/geist-LICENSE.txt +93 -93
  8. package/dist/image-to-pixel.bundle.esm.js +13 -13
  9. package/dist/image-to-pixel.bundle.esm.js.map +2 -2
  10. package/dist/image-to-pixel.cjs +2 -2
  11. package/dist/image-to-pixel.cjs.map +2 -2
  12. package/dist/image-to-pixel.esm.js +2 -2
  13. package/dist/image-to-pixel.esm.js.map +2 -2
  14. package/dist/image-to-pixel.js +2 -2
  15. package/dist/image-to-pixel.js.map +2 -2
  16. package/dist/image-to-pixel.min.js +11 -11
  17. package/dist/image-to-pixel.min.js.map +2 -2
  18. package/dist/video.esm.js +1 -1
  19. package/dist/video.esm.js.map +1 -1
  20. package/lib/core/color.js +69 -69
  21. package/lib/core/cpu/pipeline.js +21 -21
  22. package/lib/core/cpu/stages.js +227 -227
  23. package/lib/core/dither/bluenoise-data.js +3 -3
  24. package/lib/core/dither/kernels.js +27 -27
  25. package/lib/core/dither/matrices.js +95 -95
  26. package/lib/core/engine.js +158 -158
  27. package/lib/core/export/canvas.js +22 -22
  28. package/lib/core/export/crc32.js +15 -15
  29. package/lib/core/export/gif.js +153 -153
  30. package/lib/core/export/png.js +94 -94
  31. package/lib/core/export/scale.js +103 -103
  32. package/lib/core/export/sheet.js +95 -95
  33. package/lib/core/export/zip.js +55 -55
  34. package/lib/core/gpu/backend.js +159 -159
  35. package/lib/core/gpu/shaders.js +258 -258
  36. package/lib/core/palette/builtin.js +36 -36
  37. package/lib/core/palette/extract.js +69 -69
  38. package/lib/core/palette/lospec.js +24 -24
  39. package/lib/core/palette/parse.js +112 -112
  40. package/lib/core/plan.js +58 -58
  41. package/lib/core/presets.js +42 -42
  42. package/lib/core/settings.js +274 -274
  43. package/lib/core/source.js +89 -89
  44. package/lib/core/video/frames.js +144 -144
  45. package/lib/core/video/mux-mp4.js +131 -131
  46. package/lib/core/video/mux-webm.js +127 -127
  47. package/lib/editor/controls.js +139 -139
  48. package/lib/editor/dom.js +65 -65
  49. package/lib/editor/exporter.js +185 -185
  50. package/lib/editor/icons.js +61 -61
  51. package/lib/editor/index.js +793 -793
  52. package/lib/editor/inspector.js +328 -328
  53. package/lib/editor/library.js +182 -182
  54. package/lib/editor/persist.js +18 -18
  55. package/lib/editor/samples.js +90 -90
  56. package/lib/editor/stage.js +239 -239
  57. package/lib/editor/store.js +80 -80
  58. package/lib/editor/styles/editor.css +385 -385
  59. package/lib/editor/styles/fonts/README.md +6 -6
  60. package/lib/editor/styles/fonts/geist-LICENSE.txt +93 -93
  61. package/lib/editor/styles/tokens.css +89 -89
  62. package/lib/editor/ui.js +97 -97
  63. package/lib/index.js +3 -1
  64. package/lib/video.js +92 -92
  65. package/package.json +92 -92
  66. package/types/editor.d.ts +34 -34
  67. package/types/index.d.ts +196 -196
  68. package/types/video.d.ts +43 -43
package/dist/video.esm.js CHANGED
@@ -1,3 +1,3 @@
1
- /*! image-to-pixel v3 | MIT | https://github.com/Tezumie/Image-to-Pixel */
1
+ /*! image-to-pixel v3.0.1 | MIT | https://github.com/Tezumie/Image-to-Pixel */
2
2
  var W=new TextEncoder,O=class{constructor(){this.parts=[],this.size=0}u8(e){return this.bytes([e&255])}u16(e){return this.bytes([e>>>8&255,e&255])}u32(e){return this.bytes([e>>>24&255,e>>>16&255,e>>>8&255,e&255])}str(e){return this.bytes(W.encode(e))}bytes(e){let o=e instanceof Uint8Array?e:Uint8Array.from(e);return this.parts.push(o),this.size+=o.length,this}zeros(e){return this.bytes(new Uint8Array(e))}out(){let e=new Uint8Array(this.size),o=0;for(let n of this.parts)e.set(n,o),o+=n.length;return e}};function D(t,e){let o=new O;e(o);let n=o.out(),r=new O().u32(n.length+8).str(t).out(),s=new Uint8Array(n.length+8);return s.set(r),s.set(n,8),s}var V=(t,e,o,n)=>D(t,r=>{r.u8(e).u8(o>>16).u8(o>>8).u8(o),n(r)}),j=[65536,0,0,0,65536,0,0,0,1073741824];function X({width:t,height:e,timescale:o=9e4}){let n=[],r=null;return{addChunk(s,a){if(a&&a.decoderConfig&&a.decoderConfig.description&&!r){let u=a.decoderConfig.description;r=new Uint8Array(u instanceof ArrayBuffer?u:u.buffer.slice(u.byteOffset,u.byteOffset+u.byteLength))}let i=new Uint8Array(s.byteLength);s.copyTo(i),n.push({data:i,key:s.type==="key",ts:s.timestamp,dur:s.duration||0})},addSample(s,{key:a,timestamp:i,duration:u}){n.push({data:s,key:!!a,ts:i,dur:u||0})},setDescription(s){r=s},finish(){if(!n.length)throw new Error("No video frames were encoded");if(!r)throw new Error("The encoder gave no H.264 decoder configuration (avcC)");let s=f=>Math.round(f*o/1e6),a=n.map(f=>s(f.ts)),i=a.slice().sort((f,d)=>f-d),u=s(n[n.length-1].dur)||(i.length>1?i[i.length-1]-i[i.length-2]:s(33333)),m=i.map((f,d)=>d+1<i.length?i[d+1]-f:u),x=a.map((f,d)=>f-i[d]),l=x.some(f=>f!==0),M=m.reduce((f,d)=>f+d,0),E=Math.round(M*1e3/o),g=[];for(let f of m){let d=g[g.length-1];d&&d[1]===f?d[0]++:g.push([1,f])}let C=[];for(let f of x){let d=C[C.length-1];d&&d[1]===f?d[0]++:C.push([1,f])}let h=n.map((f,d)=>f.key?d+1:0).filter(Boolean),b=n.reduce((f,d)=>f+d.data.length,0),A=D("ftyp",f=>f.str("isom").u32(512).str("isom").str("iso2").str("avc1").str("mp41")),F=f=>D("moov",d=>{d.bytes(V("mvhd",0,0,z=>{z.u32(0).u32(0).u32(1e3).u32(E).u32(65536).u16(256).zeros(10),j.forEach(B=>z.u32(B)),z.zeros(24).u32(2)})),d.bytes(D("trak",z=>{z.bytes(V("tkhd",0,3,B=>{B.u32(0).u32(0).u32(1).u32(0).u32(E).zeros(8).u16(0).u16(0).u16(0).u16(0),j.forEach(I=>B.u32(I)),B.u32(t<<16).u32(e<<16)})),z.bytes(D("mdia",B=>{B.bytes(V("mdhd",0,0,I=>I.u32(0).u32(0).u32(o).u32(M).u16(21956).u16(0))),B.bytes(V("hdlr",0,0,I=>I.u32(0).str("vide").zeros(12).str("VideoHandler").u8(0))),B.bytes(D("minf",I=>{I.bytes(V("vmhd",0,1,k=>k.u16(0).u16(0).u16(0).u16(0))),I.bytes(D("dinf",k=>k.bytes(V("dref",0,0,y=>{y.u32(1),y.bytes(V("url ",0,1,()=>{}))})))),I.bytes(D("stbl",k=>{k.bytes(V("stsd",0,0,y=>{y.u32(1),y.bytes(D("avc1",P=>{P.zeros(6).u16(1).zeros(16).u16(t).u16(e).u32(4718592).u32(4718592).u32(0).u16(1);let U=W.encode("image-to-pixel").slice(0,31);P.u8(U.length).bytes(U).zeros(31-U.length).u16(24).u16(65535),P.bytes(D("avcC",st=>st.bytes(r)))}))})),k.bytes(V("stts",0,0,y=>{y.u32(g.length);for(let[P,U]of g)y.u32(P).u32(U)})),l&&k.bytes(V("ctts",1,0,y=>{y.u32(C.length);for(let[P,U]of C)y.u32(P).u32(U>>>0)})),h.length<n.length&&k.bytes(V("stss",0,0,y=>{y.u32(h.length);for(let P of h)y.u32(P)})),k.bytes(V("stsc",0,0,y=>y.u32(1).u32(1).u32(n.length).u32(1))),k.bytes(V("stsz",0,0,y=>{y.u32(0).u32(n.length);for(let P of n)y.u32(P.data.length)})),k.bytes(V("stco",0,0,y=>y.u32(1).u32(f)))}))}))}))}))}),G=F(0).length,H=A.length+G+8;if(H+b>4294967295)throw new Error("Video is larger than 4 GB");let R=F(H),w=new Uint8Array(H+b);w.set(A,0),w.set(R,A.length),new DataView(w.buffer).setUint32(A.length+R.length,b+8),w.set(W.encode("mdat"),A.length+R.length+4);let S=H;for(let f of n)w.set(f.data,S),S+=f.data.length;return w}}}var at=new TextEncoder;function ct(t){let e=new Uint8Array(8);e[0]=1;let o=t;for(let n=7;n>=1;n--)e[n]=o%256,o=Math.floor(o/256);return e}function Q(t){let e=[];for(let o=t;o>0;o=Math.floor(o/256))e.unshift(o%256);return e}function q(t){let e=0;for(let r of t)e+=r.length;let o=new Uint8Array(e),n=0;for(let r of t)o.set(r,n),n+=r.length;return o}var v=(t,e)=>q([Uint8Array.from(Q(t)),ct(e.length),e]),p=(t,e,o=0)=>{let n=[];for(let r=e;r>0||n.length===0;r=Math.floor(r/256))n.unshift(r%256);for(;n.length<o;)n.unshift(0);return v(t,Uint8Array.from(n))},N=(t,e)=>v(t,at.encode(e)),it=(t,e)=>{let o=new Uint8Array(8);return new DataView(o.buffer).setFloat64(0,e),v(t,o)},T=(t,e)=>v(t,q(e)),c={EBML:440786851,EBMLVersion:17030,EBMLReadVersion:17143,EBMLMaxIDLength:17138,EBMLMaxSizeLength:17139,DocType:17026,DocTypeVersion:17031,DocTypeReadVersion:17029,Segment:408125543,SeekHead:290298740,Seek:19899,SeekID:21419,SeekPosition:21420,Info:357149030,TimestampScale:2807729,Duration:17545,MuxingApp:19840,WritingApp:22337,Tracks:374648427,TrackEntry:174,TrackNumber:215,TrackUID:29637,TrackType:131,FlagLacing:156,CodecID:134,CodecPrivate:25506,DefaultDuration:2352003,Video:224,PixelWidth:176,PixelHeight:186,Cluster:524531317,Timestamp:231,SimpleBlock:163,Cues:475249515,CuePoint:187,CueTime:179,CueTrackPositions:183,CueTrack:247,CueClusterPosition:241},ut={vp8:"V_VP8",vp9:"V_VP9",av1:"V_AV1"};function Y({width:t,height:e,codec:o="vp9",fps:n=0}){let r=[],s=null;return{addChunk(a,i){if(i&&i.decoderConfig&&i.decoderConfig.description&&!s){let m=i.decoderConfig.description;s=new Uint8Array(m instanceof ArrayBuffer?m:m.buffer.slice(m.byteOffset,m.byteOffset+m.byteLength))}let u=new Uint8Array(a.byteLength);a.copyTo(u),r.push({data:u,key:a.type==="key",ms:a.timestamp/1e3})},addFrame(a,{key:i,timestamp:u}){r.push({data:a,key:!!i,ms:u/1e3})},finish(){if(!r.length)throw new Error("No video frames were encoded");let a=n?1e3/n:r.length>1?(r[r.length-1].ms-r[0].ms)/(r.length-1):33.333,i=r[r.length-1].ms+a,u=T(c.Info,[p(c.TimestampScale,1e6),it(c.Duration,i),N(c.MuxingApp,"image-to-pixel"),N(c.WritingApp,"image-to-pixel")]),m=T(c.Tracks,[T(c.TrackEntry,[p(c.TrackNumber,1),p(c.TrackUID,1),p(c.TrackType,1),p(c.FlagLacing,0),N(c.CodecID,ut[o]||o),...s?[v(c.CodecPrivate,s)]:[],p(c.DefaultDuration,Math.round(a*1e6)),T(c.Video,[p(c.PixelWidth,t),p(c.PixelHeight,e)])])]),x=[],l=null;for(let w of r){let L=Math.round(w.ms);(!l||w.key||L-l.start>3e4)&&(l={start:L,key:w.key,blocks:[]},x.push(l));let S=new Uint8Array(4);S[0]=129,new DataView(S.buffer).setInt16(1,L-l.start),S[3]=w.key?128:0,l.blocks.push(v(c.SimpleBlock,q([S,w.data])))}let M=x.map(w=>T(c.Cluster,[p(c.Timestamp,w.start),...w.blocks])),E=(w,L,S)=>T(c.SeekHead,[[c.Info,w],[c.Tracks,L],[c.Cues,S]].map(([f,d])=>T(c.Seek,[v(c.SeekID,Uint8Array.from(Q(f))),p(c.SeekPosition,d,8)]))),C=E(0,0,0).length,h=C+u.length,b=h+m.length,A=[];x.forEach((w,L)=>{w.key&&A.push(T(c.CuePoint,[p(c.CueTime,w.start),T(c.CueTrackPositions,[p(c.CueTrack,1),p(c.CueClusterPosition,b)])])),b+=M[L].length});let F=T(c.Cues,A.length?A:[T(c.CuePoint,[p(c.CueTime,0),T(c.CueTrackPositions,[p(c.CueTrack,1),p(c.CueClusterPosition,h+m.length)])])]),G=b,H=T(c.EBML,[p(c.EBMLVersion,1),p(c.EBMLReadVersion,1),p(c.EBMLMaxIDLength,4),p(c.EBMLMaxSizeLength,8),N(c.DocType,"webm"),p(c.DocTypeVersion,4),p(c.DocTypeReadVersion,2)]),R=T(c.Segment,[E(C,h,G),u,m,...M,F]);return q([H,R])}}}function J(t){if(Array.isArray(t))return[t[0]|0,t[1]|0,t[2]|0];let e=String(t).trim().replace(/^#|^0x/i,"");if((e.length===3||e.length===4)&&(e=e.slice(0,3).split("").map(o=>o+o).join("")),!/^[0-9a-f]{6}([0-9a-f]{2})?$/i.test(e))throw new Error(`Invalid colour "${t}"`);return[parseInt(e.slice(0,2),16),parseInt(e.slice(2,4),16),parseInt(e.slice(4,6),16)]}function K(t,e=1,o={}){e=Math.max(1,Math.round(e));let n=t.width*e,r=t.height*e,s=new Uint8ClampedArray(n*r*4),a=t.data,i=t.width;for(let u=0;u<r;u++){let m=u/e|0;for(let x=0;x<n;x++){let l=(m*i+(x/e|0))*4,M=(u*n+x)*4;s[M]=a[l],s[M+1]=a[l+1],s[M+2]=a[l+2],s[M+3]=a[l+3]}}if(o.grid&&e>=3){let[u,m,x]=J(o.gridColor||"#000000"),l=o.gridAlpha??.35;for(let M=0;M<r;M++)for(let E=0;E<n;E++){if(E%e&&M%e)continue;let g=(M*n+E)*4,C=s[g+3]/255,h=l+C*(1-l);s[g]=(u*l+s[g]*C*(1-l))/h,s[g+1]=(m*l+s[g+1]*C*(1-l))/h,s[g+2]=(x*l+s[g+2]*C*(1-l))/h,s[g+3]=h*255}}return{width:n,height:r,data:s}}var ft=(t,e,o=4e3)=>new Promise((n,r)=>{let s=setTimeout(()=>{t.removeEventListener(e,a),r(new Error(`Timed out waiting for "${e}"`))},o),a=()=>{clearTimeout(s),n()};t.addEventListener(e,a,{once:!0})}),lt={1:"loading was aborted",2:"a network error stopped the download",3:"the browser could not decode it",4:"this browser can't play this video's format"};function tt(t){let e=t.error?t.error.code:0,o=new Error(lt[e]||"the video could not be loaded");return o.code=e,t.error&&t.error.message&&(o.detail=t.error.message),o}var Z=(t,e,o)=>new Promise((n,r)=>{let s=()=>{clearTimeout(u),t.removeEventListener(e,a),t.removeEventListener("error",i)},a=()=>{s(),n()},i=()=>{s(),r(tt(t))},u=setTimeout(()=>{s(),r(new Error(`no response from the video decoder after ${o/1e3} s`))},o);t.addEventListener(e,a,{once:!0}),t.addEventListener("error",i,{once:!0})});async function _(t,{timeout:e=6e4}={}){if(t.error)throw tt(t);return t.readyState<1&&await Z(t,"loadedmetadata",e),t.readyState<2&&await Z(t,"loadeddata",e).catch(o=>{if(o.code)throw o}),t}async function $(t,e){if(e=Math.max(0,Math.min(e,(t.duration||0)-1e-4)),Math.abs(t.currentTime-e)<1e-6&&t.readyState>=2)return;let o="requestVideoFrameCallback"in t?new Promise(r=>{t.requestVideoFrameCallback(()=>r()),setTimeout(r,500)}):null,n=ft(t,"seeked");t.currentTime=e,await n,o&&await o}async function dt(t,e=30){if(!("requestVideoFrameCallback"in t))return e;await _(t);let o={t:t.currentTime,muted:t.muted,paused:t.paused},n=[];try{t.muted=!0,await new Promise(a=>{let i=setTimeout(a,1200),u=(m,x)=>{n.push(x.mediaTime),n.length>=12?(clearTimeout(i),a()):t.requestVideoFrameCallback(u)};t.requestVideoFrameCallback(u),t.play().catch(()=>{clearTimeout(i),a()})})}finally{t.pause(),t.muted=o.muted,await $(t,o.t).catch(()=>{})}let r=[];for(let a=1;a<n.length;a++){let i=n[a]-n[a-1];i>.001&&r.push(i)}if(r.length<3)return e;r.sort((a,i)=>a-i);let s=1/r[Math.floor(r.length/2)];for(let a of[12,15,23.976,24,25,29.97,30,48,50,59.94,60])if(Math.abs(s-a)<.6)return a;return Math.round(s)}function et({duration:t,fps:e,start:o=0,end:n=t,maxFrames:r=1/0}){let s=[],a=Math.min(r,Math.max(1,Math.floor((n-o)*e+1e-6)));for(let i=0;i<a;i++)s.push(o+(i+.5)/e);return s}async function ht(t,e){await _(t);let o=t.paused;t.pause();let n=et({duration:t.duration,...e});for(let r=0;r<n.length;r++){if(e.signal&&e.signal.aborted)throw new DOMException("Aborted","AbortError");await $(t,n[r]),await e.onFrame({index:r,time:n[r],total:n.length})}return o||t.play().catch(()=>{}),n.length}var nt=["avc1.640033","avc1.4d0033","avc1.42003e","avc1.640028","avc1.42001f"],rt=[["vp09.00.10.08","vp9"],["vp8","vp8"]];async function Tt(t=640,e=480){let o={mp4:!1,webm:!1,webcodecs:typeof VideoEncoder<"u"};if(o.webcodecs){for(let n of nt)if((await VideoEncoder.isConfigSupported({codec:n,width:t,height:e}).catch(()=>({}))).supported){o.mp4=!0;break}for(let[n]of rt)if((await VideoEncoder.isConfigSupported({codec:n,width:t,height:e}).catch(()=>({}))).supported){o.webm=!0;break}}return o}var ot=t=>t+(t&1);async function pt(t){let e=Math.max(1,Math.round(t.scale||1)),o=ot(t.width*e),n=ot(t.height*e),r=t.fps||30,s=t.bitrate||Math.min(2e7,Math.max(2e6,o*n*r*.15));if(typeof VideoEncoder>"u")throw new Error("This browser has no WebCodecs VideoEncoder. Export a GIF or PNG sequence instead.");let a=null,i=null,u=t.format==="mp4"?nt.map(h=>[h,"avc"]):rt;for(let[h,b]of u)if((await VideoEncoder.isConfigSupported({codec:h,width:o,height:n,bitrate:s,framerate:r}).catch(()=>({}))).supported){a=h,i=b;break}if(!a)throw new Error(`No ${t.format.toUpperCase()} encoder available in this browser at ${o}\xD7${n}.`);let m=t.format==="mp4"?X({width:o,height:n}):Y({width:o,height:n,codec:i,fps:r}),x=null,l=new VideoEncoder({output:(h,b)=>m.addChunk(h,b),error:h=>{x=h}});l.configure({codec:a,width:o,height:n,bitrate:s,framerate:r,latencyMode:"quality",...t.format==="mp4"?{avc:{format:"avc"}}:{}});let M=new OffscreenCanvas(o,n),E=M.getContext("2d"),g=0,C=1e6/r;return{width:o,height:n,codec:a,async add(h){if(x)throw x;let b=e>1?K(h,e):h;E.fillStyle="#000",E.fillRect(0,0,o,n),E.putImageData(new ImageData(new Uint8ClampedArray(b.data),b.width,b.height),0,0);let A=new VideoFrame(M,{timestamp:Math.round(g*C),duration:Math.round(C)});l.encode(A,{keyFrame:g%Math.max(1,Math.round(r*2))===0}),A.close(),g++,l.encodeQueueSize>8&&await new Promise(F=>setTimeout(F,0))},async finish(){if(await l.flush(),x)throw x;return l.close(),new Blob([m.finish()],{type:t.format==="mp4"?"video/mp4":"video/webm"})}}}async function At(t,e){let o=await pt({...e,width:t[0].width,height:t[0].height});for(let n of t)await o.add(n);return o.finish()}export{X as createMp4Muxer,pt as createVideoEncoder,Y as createWebmMuxer,At as encodeVideo,dt as estimateFps,ht as forEachFrame,et as frameTimes,$ as seekTo,_ as videoReady,Tt as videoSupport};
3
3
  //# sourceMappingURL=video.esm.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../lib/core/video/mux-mp4.js", "../lib/core/video/mux-webm.js", "../lib/core/color.js", "../lib/core/export/scale.js", "../lib/core/video/frames.js", "../lib/video.js"],
4
- "sourcesContent": ["// Minimal MP4 (ISO BMFF) muxer for one H.264 video track. Everything is buffered in memory and\n// written with the 'moov' box first (\"fast start\"), so the file plays and seeks while downloading.\n// Input: AVCC samples (length-prefixed NAL units, as WebCodecs' VideoEncoder emits with\n// avc.format 'avc') plus the AVCDecoderConfigurationRecord ('description').\n\nconst ENC = new TextEncoder();\n\nclass Writer {\n constructor() { this.parts = []; this.size = 0; }\n u8(v) { return this.bytes([v & 255]); }\n u16(v) { return this.bytes([(v >>> 8) & 255, v & 255]); }\n u32(v) { return this.bytes([(v >>> 24) & 255, (v >>> 16) & 255, (v >>> 8) & 255, v & 255]); }\n str(s) { return this.bytes(ENC.encode(s)); }\n bytes(b) { const a = b instanceof Uint8Array ? b : Uint8Array.from(b); this.parts.push(a); this.size += a.length; return this; }\n zeros(n) { return this.bytes(new Uint8Array(n)); }\n out() { const r = new Uint8Array(this.size); let o = 0; for (const p of this.parts) { r.set(p, o); o += p.length; } return r; }\n}\n\n/** A box: 4-byte size, 4-char type, then the body produced by fill(w). */\nfunction box(type, fill) {\n const w = new Writer();\n fill(w);\n const body = w.out();\n const head = new Writer().u32(body.length + 8).str(type).out();\n const r = new Uint8Array(body.length + 8); r.set(head); r.set(body, 8);\n return r;\n}\nconst fullBox = (type, version, flags, fill) => box(type, w => { w.u8(version).u8(flags >> 16).u8(flags >> 8).u8(flags); fill(w); });\nconst MATRIX = [0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000];\n\n/**\n * @param {{ width: number, height: number, timescale?: number }} opts\n */\nexport function createMp4Muxer({ width, height, timescale = 90000 }) {\n const samples = []; // { data, key, ts, dur } with ts/dur in microseconds\n let description = null;\n\n return {\n /** Use with a VideoEncoder `output` callback. */\n addChunk(chunk, meta) {\n if (meta && meta.decoderConfig && meta.decoderConfig.description && !description) {\n const d = meta.decoderConfig.description;\n description = new Uint8Array(d instanceof ArrayBuffer ? d : d.buffer.slice(d.byteOffset, d.byteOffset + d.byteLength));\n }\n const data = new Uint8Array(chunk.byteLength);\n chunk.copyTo(data);\n samples.push({ data, key: chunk.type === 'key', ts: chunk.timestamp, dur: chunk.duration || 0 });\n },\n /** Lower-level: add an AVCC sample directly. ts/dur in microseconds. */\n addSample(data, { key, timestamp, duration }) { samples.push({ data, key: !!key, ts: timestamp, dur: duration || 0 }); },\n /** AVCDecoderConfigurationRecord ('avcC' contents). */\n setDescription(d) { description = d; },\n\n /** @returns {Uint8Array} the .mp4 file */\n finish() {\n if (!samples.length) throw new Error('No video frames were encoded');\n if (!description) throw new Error('The encoder gave no H.264 decoder configuration (avcC)');\n // Samples arrive in decode order with presentation timestamps (B-frames make them non-monotonic).\n // Decode times are the sorted presentation times; 'ctts' holds the difference when there is one.\n const t = us => Math.round(us * timescale / 1e6);\n const pts = samples.map(s => t(s.ts));\n const dts = pts.slice().sort((a, b) => a - b);\n const lastDur = t(samples[samples.length - 1].dur) || (dts.length > 1 ? dts[dts.length - 1] - dts[dts.length - 2] : t(33333));\n const durs = dts.map((d, i) => (i + 1 < dts.length ? dts[i + 1] - d : lastDur));\n const offsets = pts.map((p, i) => p - dts[i]);\n const hasOffsets = offsets.some(o => o !== 0);\n const mediaDur = durs.reduce((a, b) => a + b, 0);\n const movieDur = Math.round(mediaDur * 1000 / timescale);\n\n const stts = [];\n for (const d of durs) { const last = stts[stts.length - 1]; if (last && last[1] === d) last[0]++; else stts.push([1, d]); }\n const ctts = [];\n for (const o of offsets) { const last = ctts[ctts.length - 1]; if (last && last[1] === o) last[0]++; else ctts.push([1, o]); }\n const keys = samples.map((s, i) => (s.key ? i + 1 : 0)).filter(Boolean);\n const dataSize = samples.reduce((a, s) => a + s.data.length, 0);\n\n const ftyp = box('ftyp', w => w.str('isom').u32(512).str('isom').str('iso2').str('avc1').str('mp41'));\n const buildMoov = chunkOffset => box('moov', w => {\n w.bytes(fullBox('mvhd', 0, 0, b => {\n b.u32(0).u32(0).u32(1000).u32(movieDur).u32(0x00010000).u16(0x0100).zeros(10);\n MATRIX.forEach(v => b.u32(v)); b.zeros(24).u32(2);\n }));\n w.bytes(box('trak', tr => {\n tr.bytes(fullBox('tkhd', 0, 3, b => {\n b.u32(0).u32(0).u32(1).u32(0).u32(movieDur).zeros(8).u16(0).u16(0).u16(0).u16(0);\n MATRIX.forEach(v => b.u32(v)); b.u32(width << 16).u32(height << 16);\n }));\n tr.bytes(box('mdia', md => {\n md.bytes(fullBox('mdhd', 0, 0, b => b.u32(0).u32(0).u32(timescale).u32(mediaDur).u16(0x55c4).u16(0)));\n md.bytes(fullBox('hdlr', 0, 0, b => b.u32(0).str('vide').zeros(12).str('VideoHandler').u8(0)));\n md.bytes(box('minf', mi => {\n mi.bytes(fullBox('vmhd', 0, 1, b => b.u16(0).u16(0).u16(0).u16(0)));\n mi.bytes(box('dinf', di => di.bytes(fullBox('dref', 0, 0, b => { b.u32(1); b.bytes(fullBox('url ', 0, 1, () => { })); }))));\n mi.bytes(box('stbl', st => {\n st.bytes(fullBox('stsd', 0, 0, b => {\n b.u32(1);\n b.bytes(box('avc1', a => {\n a.zeros(6).u16(1).zeros(16).u16(width).u16(height).u32(0x00480000).u32(0x00480000).u32(0).u16(1);\n const name = ENC.encode('image-to-pixel').slice(0, 31);\n a.u8(name.length).bytes(name).zeros(31 - name.length).u16(0x0018).u16(0xffff);\n a.bytes(box('avcC', c => c.bytes(description)));\n }));\n }));\n st.bytes(fullBox('stts', 0, 0, b => { b.u32(stts.length); for (const [n, d] of stts) b.u32(n).u32(d); }));\n // version 1: signed offsets, so negative composition offsets are allowed\n if (hasOffsets) st.bytes(fullBox('ctts', 1, 0, b => { b.u32(ctts.length); for (const [n, o] of ctts) b.u32(n).u32(o >>> 0); }));\n if (keys.length < samples.length) st.bytes(fullBox('stss', 0, 0, b => { b.u32(keys.length); for (const k of keys) b.u32(k); }));\n st.bytes(fullBox('stsc', 0, 0, b => b.u32(1).u32(1).u32(samples.length).u32(1)));\n st.bytes(fullBox('stsz', 0, 0, b => { b.u32(0).u32(samples.length); for (const s of samples) b.u32(s.data.length); }));\n st.bytes(fullBox('stco', 0, 0, b => b.u32(1).u32(chunkOffset)));\n }));\n }));\n }));\n }));\n });\n // the moov size doesn't depend on the offset value, so measure once and build again\n const moovSize = buildMoov(0).length;\n const offset = ftyp.length + moovSize + 8;\n if (offset + dataSize > 0xffffffff) throw new Error('Video is larger than 4 GB');\n const moov = buildMoov(offset);\n const out = new Uint8Array(offset + dataSize);\n out.set(ftyp, 0); out.set(moov, ftyp.length);\n const dv = new DataView(out.buffer);\n dv.setUint32(ftyp.length + moov.length, dataSize + 8);\n out.set(ENC.encode('mdat'), ftyp.length + moov.length + 4);\n let o = offset;\n for (const s of samples) { out.set(s.data, o); o += s.data.length; }\n return out;\n },\n };\n}\n", "// Minimal WebM (Matroska) muxer for one VP8/VP9/AV1 video track. Buffered in memory, written with\n// a SeekHead and Cues so players can seek. Input: encoded frames from WebCodecs' VideoEncoder.\n\nconst ENC = new TextEncoder();\n\n/** EBML variable-length size, always 8 bytes (simple and valid for any size). */\nfunction size8(n) {\n const b = new Uint8Array(8);\n b[0] = 0x01;\n let v = n;\n for (let i = 7; i >= 1; i--) { b[i] = v % 256; v = Math.floor(v / 256); }\n return b;\n}\nfunction idBytes(id) {\n const out = [];\n for (let v = id; v > 0; v = Math.floor(v / 256)) out.unshift(v % 256);\n return out;\n}\nfunction concat(parts) {\n let n = 0; for (const p of parts) n += p.length;\n const out = new Uint8Array(n); let o = 0;\n for (const p of parts) { out.set(p, o); o += p.length; }\n return out;\n}\n/** An element: id, 8-byte size, data. */\nconst el = (id, data) => concat([Uint8Array.from(idBytes(id)), size8(data.length), data]);\nconst uint = (id, v, bytes = 0) => {\n const b = [];\n for (let x = v; x > 0 || b.length === 0; x = Math.floor(x / 256)) b.unshift(x % 256);\n while (b.length < bytes) b.unshift(0);\n return el(id, Uint8Array.from(b));\n};\nconst str = (id, s) => el(id, ENC.encode(s));\nconst float64 = (id, v) => { const b = new Uint8Array(8); new DataView(b.buffer).setFloat64(0, v); return el(id, b); };\nconst master = (id, children) => el(id, concat(children));\n\nconst ID = {\n EBML: 0x1A45DFA3, EBMLVersion: 0x4286, EBMLReadVersion: 0x42F7, EBMLMaxIDLength: 0x42F2, EBMLMaxSizeLength: 0x42F3,\n DocType: 0x4282, DocTypeVersion: 0x4287, DocTypeReadVersion: 0x4285,\n Segment: 0x18538067, SeekHead: 0x114D9B74, Seek: 0x4DBB, SeekID: 0x53AB, SeekPosition: 0x53AC,\n Info: 0x1549A966, TimestampScale: 0x2AD7B1, Duration: 0x4489, MuxingApp: 0x4D80, WritingApp: 0x5741,\n Tracks: 0x1654AE6B, TrackEntry: 0xAE, TrackNumber: 0xD7, TrackUID: 0x73C5, TrackType: 0x83, FlagLacing: 0x9C,\n CodecID: 0x86, CodecPrivate: 0x63A2, DefaultDuration: 0x23E383, Video: 0xE0, PixelWidth: 0xB0, PixelHeight: 0xBA,\n Cluster: 0x1F43B675, Timestamp: 0xE7, SimpleBlock: 0xA3,\n Cues: 0x1C53BB6B, CuePoint: 0xBB, CueTime: 0xB3, CueTrackPositions: 0xB7, CueTrack: 0xF7, CueClusterPosition: 0xF1,\n};\n\nconst CODEC_IDS = { vp8: 'V_VP8', vp9: 'V_VP9', av1: 'V_AV1' };\n\n/**\n * @param {{ width: number, height: number, codec: 'vp8' | 'vp9' | 'av1', fps?: number }} opts\n */\nexport function createWebmMuxer({ width, height, codec = 'vp9', fps = 0 }) {\n const frames = []; // { data, key, ms }\n let codecPrivate = null;\n\n return {\n addChunk(chunk, meta) {\n if (meta && meta.decoderConfig && meta.decoderConfig.description && !codecPrivate) {\n const d = meta.decoderConfig.description;\n codecPrivate = new Uint8Array(d instanceof ArrayBuffer ? d : d.buffer.slice(d.byteOffset, d.byteOffset + d.byteLength));\n }\n const data = new Uint8Array(chunk.byteLength);\n chunk.copyTo(data);\n frames.push({ data, key: chunk.type === 'key', ms: chunk.timestamp / 1000 });\n },\n /** Lower-level: add one encoded frame. timestamp in microseconds. */\n addFrame(data, { key, timestamp }) { frames.push({ data, key: !!key, ms: timestamp / 1000 }); },\n\n /** @returns {Uint8Array} the .webm file */\n finish() {\n if (!frames.length) throw new Error('No video frames were encoded');\n // VP8/VP9/AV1 frames from WebCodecs arrive in decode order, which is the order they're stored in\n const frameMs = fps ? 1000 / fps : (frames.length > 1 ? (frames[frames.length - 1].ms - frames[0].ms) / (frames.length - 1) : 33.333);\n const duration = frames[frames.length - 1].ms + frameMs;\n\n const info = master(ID.Info, [\n uint(ID.TimestampScale, 1000000), // 1 ms\n float64(ID.Duration, duration),\n str(ID.MuxingApp, 'image-to-pixel'), str(ID.WritingApp, 'image-to-pixel'),\n ]);\n const tracks = master(ID.Tracks, [master(ID.TrackEntry, [\n uint(ID.TrackNumber, 1), uint(ID.TrackUID, 1), uint(ID.TrackType, 1), uint(ID.FlagLacing, 0),\n str(ID.CodecID, CODEC_IDS[codec] || codec),\n ...(codecPrivate ? [el(ID.CodecPrivate, codecPrivate)] : []),\n uint(ID.DefaultDuration, Math.round(frameMs * 1e6)),\n master(ID.Video, [uint(ID.PixelWidth, width), uint(ID.PixelHeight, height)]),\n ])]);\n\n // clusters: a new one at each keyframe, and before the 16-bit relative timestamp overflows\n const clusters = [];\n let cur = null;\n for (const f of frames) {\n const t = Math.round(f.ms);\n if (!cur || f.key || t - cur.start > 30000) { cur = { start: t, key: f.key, blocks: [] }; clusters.push(cur); }\n const head = new Uint8Array(4);\n head[0] = 0x81; // track 1\n new DataView(head.buffer).setInt16(1, t - cur.start);\n head[3] = f.key ? 0x80 : 0x00;\n cur.blocks.push(el(ID.SimpleBlock, concat([head, f.data])));\n }\n const clusterBytes = clusters.map(c => master(ID.Cluster, [uint(ID.Timestamp, c.start), ...c.blocks]));\n\n // SeekHead with fixed-width positions, so its size is known before the positions are\n const seekHead = (posInfo, posTracks, posCues) => master(ID.SeekHead, [\n [ID.Info, posInfo], [ID.Tracks, posTracks], [ID.Cues, posCues],\n ].map(([id, pos]) => master(ID.Seek, [el(ID.SeekID, Uint8Array.from(idBytes(id))), uint(ID.SeekPosition, pos, 8)])));\n const shSize = seekHead(0, 0, 0).length;\n const posInfo = shSize, posTracks = posInfo + info.length;\n let pos = posTracks + tracks.length;\n const cuePoints = [];\n clusters.forEach((c, i) => {\n if (c.key) cuePoints.push(master(ID.CuePoint, [uint(ID.CueTime, c.start), master(ID.CueTrackPositions, [uint(ID.CueTrack, 1), uint(ID.CueClusterPosition, pos)])]));\n pos += clusterBytes[i].length;\n });\n const cues = master(ID.Cues, cuePoints.length ? cuePoints : [master(ID.CuePoint, [uint(ID.CueTime, 0), master(ID.CueTrackPositions, [uint(ID.CueTrack, 1), uint(ID.CueClusterPosition, posTracks + tracks.length)])])]);\n const posCues = pos;\n\n const header = master(ID.EBML, [\n uint(ID.EBMLVersion, 1), uint(ID.EBMLReadVersion, 1), uint(ID.EBMLMaxIDLength, 4), uint(ID.EBMLMaxSizeLength, 8),\n str(ID.DocType, 'webm'), uint(ID.DocTypeVersion, 4), uint(ID.DocTypeReadVersion, 2),\n ]);\n const segment = master(ID.Segment, [seekHead(posInfo, posTracks, posCues), info, tracks, ...clusterBytes, cues]);\n return concat([header, segment]);\n },\n };\n}\n", "// Colour helpers shared by the CPU path, the GPU uniforms and the editor.\n// All channel values are sRGB in 0..1 unless a name says otherwise.\n\n/** Parse '#rgb', '#rrggbb', '#rrggbbaa', 'rrggbb' or [r,g,b] (0..255) into [r, g, b] bytes. */\nexport function parseColor(c) {\n if (Array.isArray(c)) return [c[0] | 0, c[1] | 0, c[2] | 0];\n let s = String(c).trim().replace(/^#|^0x/i, '');\n if (s.length === 3 || s.length === 4) s = s.slice(0, 3).split('').map(ch => ch + ch).join('');\n if (!/^[0-9a-f]{6}([0-9a-f]{2})?$/i.test(s)) throw new Error(`Invalid colour \"${c}\"`);\n return [parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16)];\n}\n\n/** True when `c` parses as a colour. */\nexport function isColor(c) {\n try { parseColor(c); return true; } catch { return false; }\n}\n\n/** [r, g, b] bytes \u2192 '#rrggbb'. */\nexport function toHex(rgb) {\n return '#' + rgb.slice(0, 3).map(v => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0')).join('');\n}\n\n/** Normalise any colour to '#rrggbb' (lower case). */\nexport const normHex = c => toHex(parseColor(c));\n\nconst lin = c => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));\n\n/** sRGB (0..1) \u2192 OKLab [L, a, b]. Same maths as the GLSL `oklab()` in gpu/shaders.js. */\nexport function oklab(r, g, b) {\n r = lin(r); g = lin(g); b = lin(b);\n const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);\n const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);\n const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);\n return [\n 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,\n 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,\n 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s,\n ];\n}\n\n/** Relative luminance-ish weight used by saturation (Rec. 709). */\nexport const LUMA = [0.2126, 0.7152, 0.0722];\n\n/** Perceived lightness 0..1 of a hex colour; handy for picking text colours on swatches. */\nexport function lightness(hex) {\n const [r, g, b] = parseColor(hex);\n return oklab(r / 255, g / 255, b / 255)[0];\n}\n\n/**\n * Average distance (sRGB 0..1) from each palette colour to its nearest neighbour.\n * Ordered dithering scales its threshold by this so `strength` means the same thing\n * for a 2-colour and a 64-colour palette.\n */\nexport function paletteSpread(rgbs) {\n if (rgbs.length < 2) return 1;\n let sum = 0;\n for (let i = 0; i < rgbs.length; i++) {\n let best = Infinity;\n for (let j = 0; j < rgbs.length; j++) {\n if (i === j) continue;\n const dr = rgbs[i][0] - rgbs[j][0], dg = rgbs[i][1] - rgbs[j][1], db = rgbs[i][2] - rgbs[j][2];\n const d = Math.sqrt(dr * dr + dg * dg + db * db);\n if (d < best) best = d;\n }\n sum += best;\n }\n return Math.max(0.04, Math.min(1, sum / rgbs.length / 255));\n}\n", "// Pure pixel helpers (no DOM): integer upscaling, grid lines, colour counting.\n\nimport { parseColor, toHex } from '../color.js';\n\n/**\n * Nearest-neighbour integer upscale.\n * @param {{ width: number, height: number, data: ArrayLike<number> }} img\n * @param {number} k scale factor (integer \u2265 1)\n * @param {{ grid?: boolean, gridColor?: string, gridAlpha?: number }} [opts] grid: 1px lines between source pixels\n */\nexport function upscale(img, k = 1, opts = {}) {\n k = Math.max(1, Math.round(k));\n const w = img.width * k, h = img.height * k;\n const out = new Uint8ClampedArray(w * h * 4);\n const src = img.data, sw = img.width;\n for (let y = 0; y < h; y++) {\n const sy = (y / k) | 0;\n for (let x = 0; x < w; x++) {\n const so = (sy * sw + ((x / k) | 0)) * 4, o = (y * w + x) * 4;\n out[o] = src[so]; out[o + 1] = src[so + 1]; out[o + 2] = src[so + 2]; out[o + 3] = src[so + 3];\n }\n }\n if (opts.grid && k >= 3) {\n const [r, g, b] = parseColor(opts.gridColor || '#000000');\n const a = opts.gridAlpha ?? 0.35;\n for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {\n if (x % k && y % k) continue;\n const o = (y * w + x) * 4;\n const sa = out[o + 3] / 255, oa = a + sa * (1 - a);\n out[o] = (r * a + out[o] * sa * (1 - a)) / oa;\n out[o + 1] = (g * a + out[o + 1] * sa * (1 - a)) / oa;\n out[o + 2] = (b * a + out[o + 2] * sa * (1 - a)) / oa;\n out[o + 3] = oa * 255;\n }\n }\n return { width: w, height: h, data: out };\n}\n\n/**\n * Nearest-neighbour resize to any size (used to export at the source's resolution, like 2.x's\n * \"full resolution\" download). Pixels may differ by one in size when the ratio isn't whole.\n */\nexport function resizeNearest(img, width, height) {\n width = Math.max(1, Math.round(width)); height = Math.max(1, Math.round(height));\n const out = new Uint8ClampedArray(width * height * 4);\n const xs = new Int32Array(width);\n for (let x = 0; x < width; x++) xs[x] = Math.min(img.width - 1, Math.floor((x + 0.5) * img.width / width));\n for (let y = 0; y < height; y++) {\n const sy = Math.min(img.height - 1, Math.floor((y + 0.5) * img.height / height));\n for (let x = 0; x < width; x++) {\n const so = (sy * img.width + xs[x]) * 4, o = (y * width + x) * 4;\n out[o] = img.data[so]; out[o + 1] = img.data[so + 1]; out[o + 2] = img.data[so + 2]; out[o + 3] = img.data[so + 3];\n }\n }\n return { width, height, data: out };\n}\n\n/** Distinct opaque colours in an image ('#rrggbb'), in order of first appearance. */\nexport function uniqueColors(img, limit = Infinity) {\n const seen = new Set();\n const d = img.data;\n for (let o = 0; o < d.length; o += 4) {\n if (d[o + 3] === 0) continue;\n seen.add((d[o] << 16) | (d[o + 1] << 8) | d[o + 2]);\n if (seen.size > limit) break;\n }\n return [...seen].map(v => toHex([v >> 16, (v >> 8) & 255, v & 255]));\n}\n\n/** True when the image has at least one non-opaque pixel. */\nexport function hasTransparency(img) {\n for (let o = 3; o < img.data.length; o += 4) if (img.data[o] < 255) return true;\n return false;\n}\n\n/**\n * Detect the pixel-grid size of an upscaled pixel-art image (e.g. a sprite saved at 4\u00D7).\n * Returns the largest block size b such that the image is made of b\u00D7b solid blocks\n * (allowing a small tolerance for JPEG noise), or 1.\n */\nexport function detectGrid(img, tolerance = 8) {\n const { width: w, height: h, data: d } = img;\n const same = (a, b) => Math.abs(d[a] - d[b]) <= tolerance && Math.abs(d[a + 1] - d[b + 1]) <= tolerance && Math.abs(d[a + 2] - d[b + 2]) <= tolerance && Math.abs(d[a + 3] - d[b + 3]) <= tolerance;\n // run lengths along rows and columns; the block size divides all of them (mostly)\n const gcd = (a, b) => (b ? gcd(b, a % b) : a);\n let g = 0;\n const stepY = Math.max(1, Math.floor(h / 64)), stepX = Math.max(1, Math.floor(w / 64));\n for (let y = 0; y < h; y += stepY) {\n let run = 1;\n for (let x = 1; x < w; x++) {\n if (same((y * w + x) * 4, (y * w + x - 1) * 4)) run++;\n else { g = gcd(g, run); run = 1; if (g === 1) return 1; }\n }\n }\n for (let x = 0; x < w; x += stepX) {\n let run = 1;\n for (let y = 1; y < h; y++) {\n if (same((y * w + x) * 4, ((y - 1) * w + x) * 4)) run++;\n else { g = gcd(g, run); run = 1; if (g === 1) return 1; }\n }\n }\n return g || 1;\n}\n", "// Frame-accurate stepping through an HTMLVideoElement by seeking to the middle of each frame.\n// Seeking (instead of playing) means every frame is visited exactly once, regardless of how\n// long processing takes, and the same input always produces the same frames.\n\nconst wait = (el, ev, ms = 4000) => new Promise((res, rej) => {\n const t = setTimeout(() => { el.removeEventListener(ev, ok); rej(new Error(`Timed out waiting for \"${ev}\"`)); }, ms);\n const ok = () => { clearTimeout(t); res(); };\n el.addEventListener(ev, ok, { once: true });\n});\n\nconst MEDIA_ERRORS = {\n 1: 'loading was aborted',\n 2: 'a network error stopped the download',\n 3: 'the browser could not decode it',\n 4: \"this browser can't play this video's format\",\n};\n\n/** Turn a <video>'s MediaError into a readable Error (err.code is the MediaError code). */\nexport function mediaError(video) {\n const code = video.error ? video.error.code : 0;\n const err = new Error(MEDIA_ERRORS[code] || 'the video could not be loaded');\n err.code = code;\n if (video.error && video.error.message) err.detail = video.error.message;\n return err;\n}\n\n/** Resolve on `ok`, reject straight away if the media element reports an error. */\nconst waitOrFail = (video, ok, ms) => new Promise((res, rej) => {\n const done = () => { clearTimeout(t); video.removeEventListener(ok, onOk); video.removeEventListener('error', onErr); };\n const onOk = () => { done(); res(); };\n const onErr = () => { done(); rej(mediaError(video)); };\n const t = setTimeout(() => { done(); rej(new Error(`no response from the video decoder after ${ms / 1000} s`)); }, ms);\n video.addEventListener(ok, onOk, { once: true });\n video.addEventListener('error', onErr, { once: true });\n});\n\n/**\n * Wait until a video's metadata (size, duration) and first frame are available.\n * Rejects immediately with a readable message when the browser can't play the file.\n */\nexport async function videoReady(video, { timeout = 60000 } = {}) {\n if (video.error) throw mediaError(video);\n if (video.readyState < 1) await waitOrFail(video, 'loadedmetadata', timeout);\n if (video.readyState < 2) await waitOrFail(video, 'loadeddata', timeout).catch(e => { if (e.code) throw e; });\n return video;\n}\n\n/**\n * Best-effort codec name for an MP4/MOV/WebM file, read from its bytes. Used to explain playback errors.\n * @param {Blob} file\n * @returns {Promise<string>} e.g. 'H.265 (HEVC)', or '' when unknown\n */\nexport async function sniffVideoCodec(file) {\n const buf = new Uint8Array(await file.arrayBuffer());\n const NAMES = { avc1: 'H.264', avc3: 'H.264', hvc1: 'H.265 (HEVC)', hev1: 'H.265 (HEVC)', av01: 'AV1', vp09: 'VP9', vp08: 'VP8', mp4v: 'MPEG-4 Part 2', apcn: 'ProRes', apch: 'ProRes', apcs: 'ProRes', apco: 'ProRes', ap4h: 'ProRes 4444', dvh1: 'Dolby Vision', dvhe: 'Dolby Vision' };\n // MP4/MOV: the first sample entry of the 'stsd' box names the codec\n for (let i = 0; i + 24 < buf.length; i++) {\n if (buf[i] === 0x73 && buf[i + 1] === 0x74 && buf[i + 2] === 0x73 && buf[i + 3] === 0x64) {\n const type = String.fromCharCode(buf[i + 16], buf[i + 17], buf[i + 18], buf[i + 19]);\n if (NAMES[type]) return NAMES[type];\n }\n }\n // WebM/Matroska: CodecID strings such as V_VP9, V_AV1, V_MPEGH/ISO/HEVC\n const head = new TextDecoder('latin1').decode(buf.subarray(0, Math.min(buf.length, 1 << 20)));\n const m = /V_(VP8|VP9|AV1|MPEG4\\/ISO\\/AVC|MPEGH\\/ISO\\/HEVC)/.exec(head);\n if (m) return { VP8: 'VP8', VP9: 'VP9', AV1: 'AV1', 'MPEG4/ISO/AVC': 'H.264', 'MPEGH/ISO/HEVC': 'H.265 (HEVC)' }[m[1]];\n return '';\n}\n\n/** Seek and resolve once the frame at `time` is decoded and paintable. */\nexport async function seekTo(video, time) {\n time = Math.max(0, Math.min(time, (video.duration || 0) - 1e-4));\n if (Math.abs(video.currentTime - time) < 1e-6 && video.readyState >= 2) return;\n const presented = 'requestVideoFrameCallback' in video\n ? new Promise(res => { video.requestVideoFrameCallback(() => res()); setTimeout(res, 500); })\n : null;\n const seeked = wait(video, 'seeked');\n video.currentTime = time;\n await seeked;\n if (presented) await presented;\n}\n\n/**\n * Estimate the frame rate from presented frames (requestVideoFrameCallback), falling back to `fallback`.\n * Plays muted for a moment and restores the position.\n */\nexport async function estimateFps(video, fallback = 30) {\n if (!('requestVideoFrameCallback' in video)) return fallback;\n await videoReady(video);\n const was = { t: video.currentTime, muted: video.muted, paused: video.paused };\n const times = [];\n try {\n video.muted = true;\n await new Promise((res) => {\n const done = setTimeout(res, 1200);\n const cb = (_, meta) => {\n times.push(meta.mediaTime);\n if (times.length >= 12) { clearTimeout(done); res(); } else video.requestVideoFrameCallback(cb);\n };\n video.requestVideoFrameCallback(cb);\n video.play().catch(() => { clearTimeout(done); res(); });\n });\n } finally {\n video.pause();\n video.muted = was.muted;\n await seekTo(video, was.t).catch(() => { });\n }\n const deltas = [];\n for (let i = 1; i < times.length; i++) { const d = times[i] - times[i - 1]; if (d > 0.001) deltas.push(d); }\n if (deltas.length < 3) return fallback;\n deltas.sort((a, b) => a - b);\n const fps = 1 / deltas[Math.floor(deltas.length / 2)];\n // snap to common rates\n for (const r of [12, 15, 23.976, 24, 25, 29.97, 30, 48, 50, 59.94, 60]) if (Math.abs(fps - r) < 0.6) return r;\n return Math.round(fps);\n}\n\n/** Frame times (seconds, middle of each frame) for a clip. */\nexport function frameTimes({ duration, fps, start = 0, end = duration, maxFrames = Infinity }) {\n const out = [];\n const n = Math.min(maxFrames, Math.max(1, Math.floor((end - start) * fps + 1e-6)));\n for (let i = 0; i < n; i++) out.push(start + (i + 0.5) / fps);\n return out;\n}\n\n/**\n * Visit frames one by one.\n * @param {HTMLVideoElement} video\n * @param {{ fps: number, start?: number, end?: number, maxFrames?: number, signal?: AbortSignal,\n * onFrame: (info: { index: number, time: number, total: number }) => any }} opts\n */\nexport async function forEachFrame(video, opts) {\n await videoReady(video);\n const wasPaused = video.paused;\n video.pause();\n const times = frameTimes({ duration: video.duration, ...opts });\n for (let i = 0; i < times.length; i++) {\n if (opts.signal && opts.signal.aborted) throw new DOMException('Aborted', 'AbortError');\n await seekTo(video, times[i]);\n await opts.onFrame({ index: i, time: times[i], total: times.length });\n }\n if (!wasPaused) video.play().catch(() => { });\n return times.length;\n}\n", "// image-to-pixel/video: encode processed frames to MP4 (H.264) or WebM (VP9/VP8) with WebCodecs.\n// The muxers are this project's own (lib/core/video/mux-*.js): MIT, no dependencies.\n\nimport { createMp4Muxer } from './core/video/mux-mp4.js';\nimport { createWebmMuxer } from './core/video/mux-webm.js';\nimport { upscale } from './core/export/scale.js';\n\nexport { forEachFrame, estimateFps, seekTo, frameTimes, videoReady } from './core/video/frames.js';\nexport { createMp4Muxer, createWebmMuxer };\n\nconst AVC = ['avc1.640033', 'avc1.4d0033', 'avc1.42003e', 'avc1.640028', 'avc1.42001f'];\nconst VP = [['vp09.00.10.08', 'vp9'], ['vp8', 'vp8']];\n\n/** Which formats this browser can encode. */\nexport async function videoSupport(width = 640, height = 480) {\n const out = { mp4: false, webm: false, webcodecs: typeof VideoEncoder !== 'undefined' };\n if (out.webcodecs) {\n for (const codec of AVC) if ((await VideoEncoder.isConfigSupported({ codec, width, height }).catch(() => ({}))).supported) { out.mp4 = true; break; }\n for (const [codec] of VP) if ((await VideoEncoder.isConfigSupported({ codec, width, height }).catch(() => ({}))).supported) { out.webm = true; break; }\n }\n return out;\n}\n\nconst even = n => n + (n & 1);\n\n/**\n * Start an encoder; add frames one at a time (so a long clip never sits in memory), then finish().\n * @param {{ format: 'mp4' | 'webm', fps: number, width: number, height: number, scale?: number, bitrate?: number }} opts\n * width/height: size of the frames you will add (before scaling)\n */\nexport async function createVideoEncoder(opts) {\n const scale = Math.max(1, Math.round(opts.scale || 1));\n const W = even(opts.width * scale), H = even(opts.height * scale);\n const fps = opts.fps || 30;\n const bitrate = opts.bitrate || Math.min(20e6, Math.max(2e6, W * H * fps * 0.15));\n if (typeof VideoEncoder === 'undefined') throw new Error('This browser has no WebCodecs VideoEncoder. Export a GIF or PNG sequence instead.');\n\n let codec = null, muxCodec = null;\n const list = opts.format === 'mp4' ? AVC.map(c => [c, 'avc']) : VP;\n for (const [c, m] of list) {\n const ok = await VideoEncoder.isConfigSupported({ codec: c, width: W, height: H, bitrate, framerate: fps }).catch(() => ({}));\n if (ok.supported) { codec = c; muxCodec = m; break; }\n }\n if (!codec) throw new Error(`No ${opts.format.toUpperCase()} encoder available in this browser at ${W}\u00D7${H}.`);\n\n const muxer = opts.format === 'mp4'\n ? createMp4Muxer({ width: W, height: H })\n : createWebmMuxer({ width: W, height: H, codec: muxCodec, fps });\n\n let error = null;\n const encoder = new VideoEncoder({\n output: (chunk, meta) => muxer.addChunk(chunk, meta),\n error: e => { error = e; },\n });\n // avc.format 'avc': length-prefixed NAL units plus a decoder config record, which MP4 needs\n encoder.configure({ codec, width: W, height: H, bitrate, framerate: fps, latencyMode: 'quality', ...(opts.format === 'mp4' ? { avc: { format: 'avc' } } : {}) });\n\n const canvas = new OffscreenCanvas(W, H);\n const ctx = canvas.getContext('2d');\n let i = 0;\n const frameUs = 1e6 / fps;\n\n return {\n width: W, height: H, codec,\n /** @param {{ width, height, data }} img */\n async add(img) {\n if (error) throw error;\n const up = scale > 1 ? upscale(img, scale) : img;\n ctx.fillStyle = '#000'; ctx.fillRect(0, 0, W, H);\n ctx.putImageData(new ImageData(new Uint8ClampedArray(up.data), up.width, up.height), 0, 0);\n const frame = new VideoFrame(canvas, { timestamp: Math.round(i * frameUs), duration: Math.round(frameUs) });\n encoder.encode(frame, { keyFrame: i % Math.max(1, Math.round(fps * 2)) === 0 });\n frame.close();\n i++;\n if (encoder.encodeQueueSize > 8) await new Promise(r => setTimeout(r, 0));\n },\n /** @returns {Promise<Blob>} */\n async finish() {\n await encoder.flush();\n if (error) throw error;\n encoder.close();\n return new Blob([muxer.finish()], { type: opts.format === 'mp4' ? 'video/mp4' : 'video/webm' });\n },\n };\n}\n\n/** Convenience: encode an array of frames in one go. */\nexport async function encodeVideo(frames, opts) {\n const enc = await createVideoEncoder({ ...opts, width: frames[0].width, height: frames[0].height });\n for (const f of frames) await enc.add(f);\n return enc.finish();\n}\n"],
4
+ "sourcesContent": ["// Minimal MP4 (ISO BMFF) muxer for one H.264 video track. Everything is buffered in memory and\r\n// written with the 'moov' box first (\"fast start\"), so the file plays and seeks while downloading.\r\n// Input: AVCC samples (length-prefixed NAL units, as WebCodecs' VideoEncoder emits with\r\n// avc.format 'avc') plus the AVCDecoderConfigurationRecord ('description').\r\n\r\nconst ENC = new TextEncoder();\r\n\r\nclass Writer {\r\n constructor() { this.parts = []; this.size = 0; }\r\n u8(v) { return this.bytes([v & 255]); }\r\n u16(v) { return this.bytes([(v >>> 8) & 255, v & 255]); }\r\n u32(v) { return this.bytes([(v >>> 24) & 255, (v >>> 16) & 255, (v >>> 8) & 255, v & 255]); }\r\n str(s) { return this.bytes(ENC.encode(s)); }\r\n bytes(b) { const a = b instanceof Uint8Array ? b : Uint8Array.from(b); this.parts.push(a); this.size += a.length; return this; }\r\n zeros(n) { return this.bytes(new Uint8Array(n)); }\r\n out() { const r = new Uint8Array(this.size); let o = 0; for (const p of this.parts) { r.set(p, o); o += p.length; } return r; }\r\n}\r\n\r\n/** A box: 4-byte size, 4-char type, then the body produced by fill(w). */\r\nfunction box(type, fill) {\r\n const w = new Writer();\r\n fill(w);\r\n const body = w.out();\r\n const head = new Writer().u32(body.length + 8).str(type).out();\r\n const r = new Uint8Array(body.length + 8); r.set(head); r.set(body, 8);\r\n return r;\r\n}\r\nconst fullBox = (type, version, flags, fill) => box(type, w => { w.u8(version).u8(flags >> 16).u8(flags >> 8).u8(flags); fill(w); });\r\nconst MATRIX = [0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000];\r\n\r\n/**\r\n * @param {{ width: number, height: number, timescale?: number }} opts\r\n */\r\nexport function createMp4Muxer({ width, height, timescale = 90000 }) {\r\n const samples = []; // { data, key, ts, dur } with ts/dur in microseconds\r\n let description = null;\r\n\r\n return {\r\n /** Use with a VideoEncoder `output` callback. */\r\n addChunk(chunk, meta) {\r\n if (meta && meta.decoderConfig && meta.decoderConfig.description && !description) {\r\n const d = meta.decoderConfig.description;\r\n description = new Uint8Array(d instanceof ArrayBuffer ? d : d.buffer.slice(d.byteOffset, d.byteOffset + d.byteLength));\r\n }\r\n const data = new Uint8Array(chunk.byteLength);\r\n chunk.copyTo(data);\r\n samples.push({ data, key: chunk.type === 'key', ts: chunk.timestamp, dur: chunk.duration || 0 });\r\n },\r\n /** Lower-level: add an AVCC sample directly. ts/dur in microseconds. */\r\n addSample(data, { key, timestamp, duration }) { samples.push({ data, key: !!key, ts: timestamp, dur: duration || 0 }); },\r\n /** AVCDecoderConfigurationRecord ('avcC' contents). */\r\n setDescription(d) { description = d; },\r\n\r\n /** @returns {Uint8Array} the .mp4 file */\r\n finish() {\r\n if (!samples.length) throw new Error('No video frames were encoded');\r\n if (!description) throw new Error('The encoder gave no H.264 decoder configuration (avcC)');\r\n // Samples arrive in decode order with presentation timestamps (B-frames make them non-monotonic).\r\n // Decode times are the sorted presentation times; 'ctts' holds the difference when there is one.\r\n const t = us => Math.round(us * timescale / 1e6);\r\n const pts = samples.map(s => t(s.ts));\r\n const dts = pts.slice().sort((a, b) => a - b);\r\n const lastDur = t(samples[samples.length - 1].dur) || (dts.length > 1 ? dts[dts.length - 1] - dts[dts.length - 2] : t(33333));\r\n const durs = dts.map((d, i) => (i + 1 < dts.length ? dts[i + 1] - d : lastDur));\r\n const offsets = pts.map((p, i) => p - dts[i]);\r\n const hasOffsets = offsets.some(o => o !== 0);\r\n const mediaDur = durs.reduce((a, b) => a + b, 0);\r\n const movieDur = Math.round(mediaDur * 1000 / timescale);\r\n\r\n const stts = [];\r\n for (const d of durs) { const last = stts[stts.length - 1]; if (last && last[1] === d) last[0]++; else stts.push([1, d]); }\r\n const ctts = [];\r\n for (const o of offsets) { const last = ctts[ctts.length - 1]; if (last && last[1] === o) last[0]++; else ctts.push([1, o]); }\r\n const keys = samples.map((s, i) => (s.key ? i + 1 : 0)).filter(Boolean);\r\n const dataSize = samples.reduce((a, s) => a + s.data.length, 0);\r\n\r\n const ftyp = box('ftyp', w => w.str('isom').u32(512).str('isom').str('iso2').str('avc1').str('mp41'));\r\n const buildMoov = chunkOffset => box('moov', w => {\r\n w.bytes(fullBox('mvhd', 0, 0, b => {\r\n b.u32(0).u32(0).u32(1000).u32(movieDur).u32(0x00010000).u16(0x0100).zeros(10);\r\n MATRIX.forEach(v => b.u32(v)); b.zeros(24).u32(2);\r\n }));\r\n w.bytes(box('trak', tr => {\r\n tr.bytes(fullBox('tkhd', 0, 3, b => {\r\n b.u32(0).u32(0).u32(1).u32(0).u32(movieDur).zeros(8).u16(0).u16(0).u16(0).u16(0);\r\n MATRIX.forEach(v => b.u32(v)); b.u32(width << 16).u32(height << 16);\r\n }));\r\n tr.bytes(box('mdia', md => {\r\n md.bytes(fullBox('mdhd', 0, 0, b => b.u32(0).u32(0).u32(timescale).u32(mediaDur).u16(0x55c4).u16(0)));\r\n md.bytes(fullBox('hdlr', 0, 0, b => b.u32(0).str('vide').zeros(12).str('VideoHandler').u8(0)));\r\n md.bytes(box('minf', mi => {\r\n mi.bytes(fullBox('vmhd', 0, 1, b => b.u16(0).u16(0).u16(0).u16(0)));\r\n mi.bytes(box('dinf', di => di.bytes(fullBox('dref', 0, 0, b => { b.u32(1); b.bytes(fullBox('url ', 0, 1, () => { })); }))));\r\n mi.bytes(box('stbl', st => {\r\n st.bytes(fullBox('stsd', 0, 0, b => {\r\n b.u32(1);\r\n b.bytes(box('avc1', a => {\r\n a.zeros(6).u16(1).zeros(16).u16(width).u16(height).u32(0x00480000).u32(0x00480000).u32(0).u16(1);\r\n const name = ENC.encode('image-to-pixel').slice(0, 31);\r\n a.u8(name.length).bytes(name).zeros(31 - name.length).u16(0x0018).u16(0xffff);\r\n a.bytes(box('avcC', c => c.bytes(description)));\r\n }));\r\n }));\r\n st.bytes(fullBox('stts', 0, 0, b => { b.u32(stts.length); for (const [n, d] of stts) b.u32(n).u32(d); }));\r\n // version 1: signed offsets, so negative composition offsets are allowed\r\n if (hasOffsets) st.bytes(fullBox('ctts', 1, 0, b => { b.u32(ctts.length); for (const [n, o] of ctts) b.u32(n).u32(o >>> 0); }));\r\n if (keys.length < samples.length) st.bytes(fullBox('stss', 0, 0, b => { b.u32(keys.length); for (const k of keys) b.u32(k); }));\r\n st.bytes(fullBox('stsc', 0, 0, b => b.u32(1).u32(1).u32(samples.length).u32(1)));\r\n st.bytes(fullBox('stsz', 0, 0, b => { b.u32(0).u32(samples.length); for (const s of samples) b.u32(s.data.length); }));\r\n st.bytes(fullBox('stco', 0, 0, b => b.u32(1).u32(chunkOffset)));\r\n }));\r\n }));\r\n }));\r\n }));\r\n });\r\n // the moov size doesn't depend on the offset value, so measure once and build again\r\n const moovSize = buildMoov(0).length;\r\n const offset = ftyp.length + moovSize + 8;\r\n if (offset + dataSize > 0xffffffff) throw new Error('Video is larger than 4 GB');\r\n const moov = buildMoov(offset);\r\n const out = new Uint8Array(offset + dataSize);\r\n out.set(ftyp, 0); out.set(moov, ftyp.length);\r\n const dv = new DataView(out.buffer);\r\n dv.setUint32(ftyp.length + moov.length, dataSize + 8);\r\n out.set(ENC.encode('mdat'), ftyp.length + moov.length + 4);\r\n let o = offset;\r\n for (const s of samples) { out.set(s.data, o); o += s.data.length; }\r\n return out;\r\n },\r\n };\r\n}\r\n", "// Minimal WebM (Matroska) muxer for one VP8/VP9/AV1 video track. Buffered in memory, written with\r\n// a SeekHead and Cues so players can seek. Input: encoded frames from WebCodecs' VideoEncoder.\r\n\r\nconst ENC = new TextEncoder();\r\n\r\n/** EBML variable-length size, always 8 bytes (simple and valid for any size). */\r\nfunction size8(n) {\r\n const b = new Uint8Array(8);\r\n b[0] = 0x01;\r\n let v = n;\r\n for (let i = 7; i >= 1; i--) { b[i] = v % 256; v = Math.floor(v / 256); }\r\n return b;\r\n}\r\nfunction idBytes(id) {\r\n const out = [];\r\n for (let v = id; v > 0; v = Math.floor(v / 256)) out.unshift(v % 256);\r\n return out;\r\n}\r\nfunction concat(parts) {\r\n let n = 0; for (const p of parts) n += p.length;\r\n const out = new Uint8Array(n); let o = 0;\r\n for (const p of parts) { out.set(p, o); o += p.length; }\r\n return out;\r\n}\r\n/** An element: id, 8-byte size, data. */\r\nconst el = (id, data) => concat([Uint8Array.from(idBytes(id)), size8(data.length), data]);\r\nconst uint = (id, v, bytes = 0) => {\r\n const b = [];\r\n for (let x = v; x > 0 || b.length === 0; x = Math.floor(x / 256)) b.unshift(x % 256);\r\n while (b.length < bytes) b.unshift(0);\r\n return el(id, Uint8Array.from(b));\r\n};\r\nconst str = (id, s) => el(id, ENC.encode(s));\r\nconst float64 = (id, v) => { const b = new Uint8Array(8); new DataView(b.buffer).setFloat64(0, v); return el(id, b); };\r\nconst master = (id, children) => el(id, concat(children));\r\n\r\nconst ID = {\r\n EBML: 0x1A45DFA3, EBMLVersion: 0x4286, EBMLReadVersion: 0x42F7, EBMLMaxIDLength: 0x42F2, EBMLMaxSizeLength: 0x42F3,\r\n DocType: 0x4282, DocTypeVersion: 0x4287, DocTypeReadVersion: 0x4285,\r\n Segment: 0x18538067, SeekHead: 0x114D9B74, Seek: 0x4DBB, SeekID: 0x53AB, SeekPosition: 0x53AC,\r\n Info: 0x1549A966, TimestampScale: 0x2AD7B1, Duration: 0x4489, MuxingApp: 0x4D80, WritingApp: 0x5741,\r\n Tracks: 0x1654AE6B, TrackEntry: 0xAE, TrackNumber: 0xD7, TrackUID: 0x73C5, TrackType: 0x83, FlagLacing: 0x9C,\r\n CodecID: 0x86, CodecPrivate: 0x63A2, DefaultDuration: 0x23E383, Video: 0xE0, PixelWidth: 0xB0, PixelHeight: 0xBA,\r\n Cluster: 0x1F43B675, Timestamp: 0xE7, SimpleBlock: 0xA3,\r\n Cues: 0x1C53BB6B, CuePoint: 0xBB, CueTime: 0xB3, CueTrackPositions: 0xB7, CueTrack: 0xF7, CueClusterPosition: 0xF1,\r\n};\r\n\r\nconst CODEC_IDS = { vp8: 'V_VP8', vp9: 'V_VP9', av1: 'V_AV1' };\r\n\r\n/**\r\n * @param {{ width: number, height: number, codec: 'vp8' | 'vp9' | 'av1', fps?: number }} opts\r\n */\r\nexport function createWebmMuxer({ width, height, codec = 'vp9', fps = 0 }) {\r\n const frames = []; // { data, key, ms }\r\n let codecPrivate = null;\r\n\r\n return {\r\n addChunk(chunk, meta) {\r\n if (meta && meta.decoderConfig && meta.decoderConfig.description && !codecPrivate) {\r\n const d = meta.decoderConfig.description;\r\n codecPrivate = new Uint8Array(d instanceof ArrayBuffer ? d : d.buffer.slice(d.byteOffset, d.byteOffset + d.byteLength));\r\n }\r\n const data = new Uint8Array(chunk.byteLength);\r\n chunk.copyTo(data);\r\n frames.push({ data, key: chunk.type === 'key', ms: chunk.timestamp / 1000 });\r\n },\r\n /** Lower-level: add one encoded frame. timestamp in microseconds. */\r\n addFrame(data, { key, timestamp }) { frames.push({ data, key: !!key, ms: timestamp / 1000 }); },\r\n\r\n /** @returns {Uint8Array} the .webm file */\r\n finish() {\r\n if (!frames.length) throw new Error('No video frames were encoded');\r\n // VP8/VP9/AV1 frames from WebCodecs arrive in decode order, which is the order they're stored in\r\n const frameMs = fps ? 1000 / fps : (frames.length > 1 ? (frames[frames.length - 1].ms - frames[0].ms) / (frames.length - 1) : 33.333);\r\n const duration = frames[frames.length - 1].ms + frameMs;\r\n\r\n const info = master(ID.Info, [\r\n uint(ID.TimestampScale, 1000000), // 1 ms\r\n float64(ID.Duration, duration),\r\n str(ID.MuxingApp, 'image-to-pixel'), str(ID.WritingApp, 'image-to-pixel'),\r\n ]);\r\n const tracks = master(ID.Tracks, [master(ID.TrackEntry, [\r\n uint(ID.TrackNumber, 1), uint(ID.TrackUID, 1), uint(ID.TrackType, 1), uint(ID.FlagLacing, 0),\r\n str(ID.CodecID, CODEC_IDS[codec] || codec),\r\n ...(codecPrivate ? [el(ID.CodecPrivate, codecPrivate)] : []),\r\n uint(ID.DefaultDuration, Math.round(frameMs * 1e6)),\r\n master(ID.Video, [uint(ID.PixelWidth, width), uint(ID.PixelHeight, height)]),\r\n ])]);\r\n\r\n // clusters: a new one at each keyframe, and before the 16-bit relative timestamp overflows\r\n const clusters = [];\r\n let cur = null;\r\n for (const f of frames) {\r\n const t = Math.round(f.ms);\r\n if (!cur || f.key || t - cur.start > 30000) { cur = { start: t, key: f.key, blocks: [] }; clusters.push(cur); }\r\n const head = new Uint8Array(4);\r\n head[0] = 0x81; // track 1\r\n new DataView(head.buffer).setInt16(1, t - cur.start);\r\n head[3] = f.key ? 0x80 : 0x00;\r\n cur.blocks.push(el(ID.SimpleBlock, concat([head, f.data])));\r\n }\r\n const clusterBytes = clusters.map(c => master(ID.Cluster, [uint(ID.Timestamp, c.start), ...c.blocks]));\r\n\r\n // SeekHead with fixed-width positions, so its size is known before the positions are\r\n const seekHead = (posInfo, posTracks, posCues) => master(ID.SeekHead, [\r\n [ID.Info, posInfo], [ID.Tracks, posTracks], [ID.Cues, posCues],\r\n ].map(([id, pos]) => master(ID.Seek, [el(ID.SeekID, Uint8Array.from(idBytes(id))), uint(ID.SeekPosition, pos, 8)])));\r\n const shSize = seekHead(0, 0, 0).length;\r\n const posInfo = shSize, posTracks = posInfo + info.length;\r\n let pos = posTracks + tracks.length;\r\n const cuePoints = [];\r\n clusters.forEach((c, i) => {\r\n if (c.key) cuePoints.push(master(ID.CuePoint, [uint(ID.CueTime, c.start), master(ID.CueTrackPositions, [uint(ID.CueTrack, 1), uint(ID.CueClusterPosition, pos)])]));\r\n pos += clusterBytes[i].length;\r\n });\r\n const cues = master(ID.Cues, cuePoints.length ? cuePoints : [master(ID.CuePoint, [uint(ID.CueTime, 0), master(ID.CueTrackPositions, [uint(ID.CueTrack, 1), uint(ID.CueClusterPosition, posTracks + tracks.length)])])]);\r\n const posCues = pos;\r\n\r\n const header = master(ID.EBML, [\r\n uint(ID.EBMLVersion, 1), uint(ID.EBMLReadVersion, 1), uint(ID.EBMLMaxIDLength, 4), uint(ID.EBMLMaxSizeLength, 8),\r\n str(ID.DocType, 'webm'), uint(ID.DocTypeVersion, 4), uint(ID.DocTypeReadVersion, 2),\r\n ]);\r\n const segment = master(ID.Segment, [seekHead(posInfo, posTracks, posCues), info, tracks, ...clusterBytes, cues]);\r\n return concat([header, segment]);\r\n },\r\n };\r\n}\r\n", "// Colour helpers shared by the CPU path, the GPU uniforms and the editor.\r\n// All channel values are sRGB in 0..1 unless a name says otherwise.\r\n\r\n/** Parse '#rgb', '#rrggbb', '#rrggbbaa', 'rrggbb' or [r,g,b] (0..255) into [r, g, b] bytes. */\r\nexport function parseColor(c) {\r\n if (Array.isArray(c)) return [c[0] | 0, c[1] | 0, c[2] | 0];\r\n let s = String(c).trim().replace(/^#|^0x/i, '');\r\n if (s.length === 3 || s.length === 4) s = s.slice(0, 3).split('').map(ch => ch + ch).join('');\r\n if (!/^[0-9a-f]{6}([0-9a-f]{2})?$/i.test(s)) throw new Error(`Invalid colour \"${c}\"`);\r\n return [parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16)];\r\n}\r\n\r\n/** True when `c` parses as a colour. */\r\nexport function isColor(c) {\r\n try { parseColor(c); return true; } catch { return false; }\r\n}\r\n\r\n/** [r, g, b] bytes \u2192 '#rrggbb'. */\r\nexport function toHex(rgb) {\r\n return '#' + rgb.slice(0, 3).map(v => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0')).join('');\r\n}\r\n\r\n/** Normalise any colour to '#rrggbb' (lower case). */\r\nexport const normHex = c => toHex(parseColor(c));\r\n\r\nconst lin = c => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));\r\n\r\n/** sRGB (0..1) \u2192 OKLab [L, a, b]. Same maths as the GLSL `oklab()` in gpu/shaders.js. */\r\nexport function oklab(r, g, b) {\r\n r = lin(r); g = lin(g); b = lin(b);\r\n const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);\r\n const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);\r\n const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);\r\n return [\r\n 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,\r\n 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,\r\n 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s,\r\n ];\r\n}\r\n\r\n/** Relative luminance-ish weight used by saturation (Rec. 709). */\r\nexport const LUMA = [0.2126, 0.7152, 0.0722];\r\n\r\n/** Perceived lightness 0..1 of a hex colour; handy for picking text colours on swatches. */\r\nexport function lightness(hex) {\r\n const [r, g, b] = parseColor(hex);\r\n return oklab(r / 255, g / 255, b / 255)[0];\r\n}\r\n\r\n/**\r\n * Average distance (sRGB 0..1) from each palette colour to its nearest neighbour.\r\n * Ordered dithering scales its threshold by this so `strength` means the same thing\r\n * for a 2-colour and a 64-colour palette.\r\n */\r\nexport function paletteSpread(rgbs) {\r\n if (rgbs.length < 2) return 1;\r\n let sum = 0;\r\n for (let i = 0; i < rgbs.length; i++) {\r\n let best = Infinity;\r\n for (let j = 0; j < rgbs.length; j++) {\r\n if (i === j) continue;\r\n const dr = rgbs[i][0] - rgbs[j][0], dg = rgbs[i][1] - rgbs[j][1], db = rgbs[i][2] - rgbs[j][2];\r\n const d = Math.sqrt(dr * dr + dg * dg + db * db);\r\n if (d < best) best = d;\r\n }\r\n sum += best;\r\n }\r\n return Math.max(0.04, Math.min(1, sum / rgbs.length / 255));\r\n}\r\n", "// Pure pixel helpers (no DOM): integer upscaling, grid lines, colour counting.\r\n\r\nimport { parseColor, toHex } from '../color.js';\r\n\r\n/**\r\n * Nearest-neighbour integer upscale.\r\n * @param {{ width: number, height: number, data: ArrayLike<number> }} img\r\n * @param {number} k scale factor (integer \u2265 1)\r\n * @param {{ grid?: boolean, gridColor?: string, gridAlpha?: number }} [opts] grid: 1px lines between source pixels\r\n */\r\nexport function upscale(img, k = 1, opts = {}) {\r\n k = Math.max(1, Math.round(k));\r\n const w = img.width * k, h = img.height * k;\r\n const out = new Uint8ClampedArray(w * h * 4);\r\n const src = img.data, sw = img.width;\r\n for (let y = 0; y < h; y++) {\r\n const sy = (y / k) | 0;\r\n for (let x = 0; x < w; x++) {\r\n const so = (sy * sw + ((x / k) | 0)) * 4, o = (y * w + x) * 4;\r\n out[o] = src[so]; out[o + 1] = src[so + 1]; out[o + 2] = src[so + 2]; out[o + 3] = src[so + 3];\r\n }\r\n }\r\n if (opts.grid && k >= 3) {\r\n const [r, g, b] = parseColor(opts.gridColor || '#000000');\r\n const a = opts.gridAlpha ?? 0.35;\r\n for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {\r\n if (x % k && y % k) continue;\r\n const o = (y * w + x) * 4;\r\n const sa = out[o + 3] / 255, oa = a + sa * (1 - a);\r\n out[o] = (r * a + out[o] * sa * (1 - a)) / oa;\r\n out[o + 1] = (g * a + out[o + 1] * sa * (1 - a)) / oa;\r\n out[o + 2] = (b * a + out[o + 2] * sa * (1 - a)) / oa;\r\n out[o + 3] = oa * 255;\r\n }\r\n }\r\n return { width: w, height: h, data: out };\r\n}\r\n\r\n/**\r\n * Nearest-neighbour resize to any size (used to export at the source's resolution, like 2.x's\r\n * \"full resolution\" download). Pixels may differ by one in size when the ratio isn't whole.\r\n */\r\nexport function resizeNearest(img, width, height) {\r\n width = Math.max(1, Math.round(width)); height = Math.max(1, Math.round(height));\r\n const out = new Uint8ClampedArray(width * height * 4);\r\n const xs = new Int32Array(width);\r\n for (let x = 0; x < width; x++) xs[x] = Math.min(img.width - 1, Math.floor((x + 0.5) * img.width / width));\r\n for (let y = 0; y < height; y++) {\r\n const sy = Math.min(img.height - 1, Math.floor((y + 0.5) * img.height / height));\r\n for (let x = 0; x < width; x++) {\r\n const so = (sy * img.width + xs[x]) * 4, o = (y * width + x) * 4;\r\n out[o] = img.data[so]; out[o + 1] = img.data[so + 1]; out[o + 2] = img.data[so + 2]; out[o + 3] = img.data[so + 3];\r\n }\r\n }\r\n return { width, height, data: out };\r\n}\r\n\r\n/** Distinct opaque colours in an image ('#rrggbb'), in order of first appearance. */\r\nexport function uniqueColors(img, limit = Infinity) {\r\n const seen = new Set();\r\n const d = img.data;\r\n for (let o = 0; o < d.length; o += 4) {\r\n if (d[o + 3] === 0) continue;\r\n seen.add((d[o] << 16) | (d[o + 1] << 8) | d[o + 2]);\r\n if (seen.size > limit) break;\r\n }\r\n return [...seen].map(v => toHex([v >> 16, (v >> 8) & 255, v & 255]));\r\n}\r\n\r\n/** True when the image has at least one non-opaque pixel. */\r\nexport function hasTransparency(img) {\r\n for (let o = 3; o < img.data.length; o += 4) if (img.data[o] < 255) return true;\r\n return false;\r\n}\r\n\r\n/**\r\n * Detect the pixel-grid size of an upscaled pixel-art image (e.g. a sprite saved at 4\u00D7).\r\n * Returns the largest block size b such that the image is made of b\u00D7b solid blocks\r\n * (allowing a small tolerance for JPEG noise), or 1.\r\n */\r\nexport function detectGrid(img, tolerance = 8) {\r\n const { width: w, height: h, data: d } = img;\r\n const same = (a, b) => Math.abs(d[a] - d[b]) <= tolerance && Math.abs(d[a + 1] - d[b + 1]) <= tolerance && Math.abs(d[a + 2] - d[b + 2]) <= tolerance && Math.abs(d[a + 3] - d[b + 3]) <= tolerance;\r\n // run lengths along rows and columns; the block size divides all of them (mostly)\r\n const gcd = (a, b) => (b ? gcd(b, a % b) : a);\r\n let g = 0;\r\n const stepY = Math.max(1, Math.floor(h / 64)), stepX = Math.max(1, Math.floor(w / 64));\r\n for (let y = 0; y < h; y += stepY) {\r\n let run = 1;\r\n for (let x = 1; x < w; x++) {\r\n if (same((y * w + x) * 4, (y * w + x - 1) * 4)) run++;\r\n else { g = gcd(g, run); run = 1; if (g === 1) return 1; }\r\n }\r\n }\r\n for (let x = 0; x < w; x += stepX) {\r\n let run = 1;\r\n for (let y = 1; y < h; y++) {\r\n if (same((y * w + x) * 4, ((y - 1) * w + x) * 4)) run++;\r\n else { g = gcd(g, run); run = 1; if (g === 1) return 1; }\r\n }\r\n }\r\n return g || 1;\r\n}\r\n", "// Frame-accurate stepping through an HTMLVideoElement by seeking to the middle of each frame.\r\n// Seeking (instead of playing) means every frame is visited exactly once, regardless of how\r\n// long processing takes, and the same input always produces the same frames.\r\n\r\nconst wait = (el, ev, ms = 4000) => new Promise((res, rej) => {\r\n const t = setTimeout(() => { el.removeEventListener(ev, ok); rej(new Error(`Timed out waiting for \"${ev}\"`)); }, ms);\r\n const ok = () => { clearTimeout(t); res(); };\r\n el.addEventListener(ev, ok, { once: true });\r\n});\r\n\r\nconst MEDIA_ERRORS = {\r\n 1: 'loading was aborted',\r\n 2: 'a network error stopped the download',\r\n 3: 'the browser could not decode it',\r\n 4: \"this browser can't play this video's format\",\r\n};\r\n\r\n/** Turn a <video>'s MediaError into a readable Error (err.code is the MediaError code). */\r\nexport function mediaError(video) {\r\n const code = video.error ? video.error.code : 0;\r\n const err = new Error(MEDIA_ERRORS[code] || 'the video could not be loaded');\r\n err.code = code;\r\n if (video.error && video.error.message) err.detail = video.error.message;\r\n return err;\r\n}\r\n\r\n/** Resolve on `ok`, reject straight away if the media element reports an error. */\r\nconst waitOrFail = (video, ok, ms) => new Promise((res, rej) => {\r\n const done = () => { clearTimeout(t); video.removeEventListener(ok, onOk); video.removeEventListener('error', onErr); };\r\n const onOk = () => { done(); res(); };\r\n const onErr = () => { done(); rej(mediaError(video)); };\r\n const t = setTimeout(() => { done(); rej(new Error(`no response from the video decoder after ${ms / 1000} s`)); }, ms);\r\n video.addEventListener(ok, onOk, { once: true });\r\n video.addEventListener('error', onErr, { once: true });\r\n});\r\n\r\n/**\r\n * Wait until a video's metadata (size, duration) and first frame are available.\r\n * Rejects immediately with a readable message when the browser can't play the file.\r\n */\r\nexport async function videoReady(video, { timeout = 60000 } = {}) {\r\n if (video.error) throw mediaError(video);\r\n if (video.readyState < 1) await waitOrFail(video, 'loadedmetadata', timeout);\r\n if (video.readyState < 2) await waitOrFail(video, 'loadeddata', timeout).catch(e => { if (e.code) throw e; });\r\n return video;\r\n}\r\n\r\n/**\r\n * Best-effort codec name for an MP4/MOV/WebM file, read from its bytes. Used to explain playback errors.\r\n * @param {Blob} file\r\n * @returns {Promise<string>} e.g. 'H.265 (HEVC)', or '' when unknown\r\n */\r\nexport async function sniffVideoCodec(file) {\r\n const buf = new Uint8Array(await file.arrayBuffer());\r\n const NAMES = { avc1: 'H.264', avc3: 'H.264', hvc1: 'H.265 (HEVC)', hev1: 'H.265 (HEVC)', av01: 'AV1', vp09: 'VP9', vp08: 'VP8', mp4v: 'MPEG-4 Part 2', apcn: 'ProRes', apch: 'ProRes', apcs: 'ProRes', apco: 'ProRes', ap4h: 'ProRes 4444', dvh1: 'Dolby Vision', dvhe: 'Dolby Vision' };\r\n // MP4/MOV: the first sample entry of the 'stsd' box names the codec\r\n for (let i = 0; i + 24 < buf.length; i++) {\r\n if (buf[i] === 0x73 && buf[i + 1] === 0x74 && buf[i + 2] === 0x73 && buf[i + 3] === 0x64) {\r\n const type = String.fromCharCode(buf[i + 16], buf[i + 17], buf[i + 18], buf[i + 19]);\r\n if (NAMES[type]) return NAMES[type];\r\n }\r\n }\r\n // WebM/Matroska: CodecID strings such as V_VP9, V_AV1, V_MPEGH/ISO/HEVC\r\n const head = new TextDecoder('latin1').decode(buf.subarray(0, Math.min(buf.length, 1 << 20)));\r\n const m = /V_(VP8|VP9|AV1|MPEG4\\/ISO\\/AVC|MPEGH\\/ISO\\/HEVC)/.exec(head);\r\n if (m) return { VP8: 'VP8', VP9: 'VP9', AV1: 'AV1', 'MPEG4/ISO/AVC': 'H.264', 'MPEGH/ISO/HEVC': 'H.265 (HEVC)' }[m[1]];\r\n return '';\r\n}\r\n\r\n/** Seek and resolve once the frame at `time` is decoded and paintable. */\r\nexport async function seekTo(video, time) {\r\n time = Math.max(0, Math.min(time, (video.duration || 0) - 1e-4));\r\n if (Math.abs(video.currentTime - time) < 1e-6 && video.readyState >= 2) return;\r\n const presented = 'requestVideoFrameCallback' in video\r\n ? new Promise(res => { video.requestVideoFrameCallback(() => res()); setTimeout(res, 500); })\r\n : null;\r\n const seeked = wait(video, 'seeked');\r\n video.currentTime = time;\r\n await seeked;\r\n if (presented) await presented;\r\n}\r\n\r\n/**\r\n * Estimate the frame rate from presented frames (requestVideoFrameCallback), falling back to `fallback`.\r\n * Plays muted for a moment and restores the position.\r\n */\r\nexport async function estimateFps(video, fallback = 30) {\r\n if (!('requestVideoFrameCallback' in video)) return fallback;\r\n await videoReady(video);\r\n const was = { t: video.currentTime, muted: video.muted, paused: video.paused };\r\n const times = [];\r\n try {\r\n video.muted = true;\r\n await new Promise((res) => {\r\n const done = setTimeout(res, 1200);\r\n const cb = (_, meta) => {\r\n times.push(meta.mediaTime);\r\n if (times.length >= 12) { clearTimeout(done); res(); } else video.requestVideoFrameCallback(cb);\r\n };\r\n video.requestVideoFrameCallback(cb);\r\n video.play().catch(() => { clearTimeout(done); res(); });\r\n });\r\n } finally {\r\n video.pause();\r\n video.muted = was.muted;\r\n await seekTo(video, was.t).catch(() => { });\r\n }\r\n const deltas = [];\r\n for (let i = 1; i < times.length; i++) { const d = times[i] - times[i - 1]; if (d > 0.001) deltas.push(d); }\r\n if (deltas.length < 3) return fallback;\r\n deltas.sort((a, b) => a - b);\r\n const fps = 1 / deltas[Math.floor(deltas.length / 2)];\r\n // snap to common rates\r\n for (const r of [12, 15, 23.976, 24, 25, 29.97, 30, 48, 50, 59.94, 60]) if (Math.abs(fps - r) < 0.6) return r;\r\n return Math.round(fps);\r\n}\r\n\r\n/** Frame times (seconds, middle of each frame) for a clip. */\r\nexport function frameTimes({ duration, fps, start = 0, end = duration, maxFrames = Infinity }) {\r\n const out = [];\r\n const n = Math.min(maxFrames, Math.max(1, Math.floor((end - start) * fps + 1e-6)));\r\n for (let i = 0; i < n; i++) out.push(start + (i + 0.5) / fps);\r\n return out;\r\n}\r\n\r\n/**\r\n * Visit frames one by one.\r\n * @param {HTMLVideoElement} video\r\n * @param {{ fps: number, start?: number, end?: number, maxFrames?: number, signal?: AbortSignal,\r\n * onFrame: (info: { index: number, time: number, total: number }) => any }} opts\r\n */\r\nexport async function forEachFrame(video, opts) {\r\n await videoReady(video);\r\n const wasPaused = video.paused;\r\n video.pause();\r\n const times = frameTimes({ duration: video.duration, ...opts });\r\n for (let i = 0; i < times.length; i++) {\r\n if (opts.signal && opts.signal.aborted) throw new DOMException('Aborted', 'AbortError');\r\n await seekTo(video, times[i]);\r\n await opts.onFrame({ index: i, time: times[i], total: times.length });\r\n }\r\n if (!wasPaused) video.play().catch(() => { });\r\n return times.length;\r\n}\r\n", "// image-to-pixel/video: encode processed frames to MP4 (H.264) or WebM (VP9/VP8) with WebCodecs.\r\n// The muxers are this project's own (lib/core/video/mux-*.js): MIT, no dependencies.\r\n\r\nimport { createMp4Muxer } from './core/video/mux-mp4.js';\r\nimport { createWebmMuxer } from './core/video/mux-webm.js';\r\nimport { upscale } from './core/export/scale.js';\r\n\r\nexport { forEachFrame, estimateFps, seekTo, frameTimes, videoReady } from './core/video/frames.js';\r\nexport { createMp4Muxer, createWebmMuxer };\r\n\r\nconst AVC = ['avc1.640033', 'avc1.4d0033', 'avc1.42003e', 'avc1.640028', 'avc1.42001f'];\r\nconst VP = [['vp09.00.10.08', 'vp9'], ['vp8', 'vp8']];\r\n\r\n/** Which formats this browser can encode. */\r\nexport async function videoSupport(width = 640, height = 480) {\r\n const out = { mp4: false, webm: false, webcodecs: typeof VideoEncoder !== 'undefined' };\r\n if (out.webcodecs) {\r\n for (const codec of AVC) if ((await VideoEncoder.isConfigSupported({ codec, width, height }).catch(() => ({}))).supported) { out.mp4 = true; break; }\r\n for (const [codec] of VP) if ((await VideoEncoder.isConfigSupported({ codec, width, height }).catch(() => ({}))).supported) { out.webm = true; break; }\r\n }\r\n return out;\r\n}\r\n\r\nconst even = n => n + (n & 1);\r\n\r\n/**\r\n * Start an encoder; add frames one at a time (so a long clip never sits in memory), then finish().\r\n * @param {{ format: 'mp4' | 'webm', fps: number, width: number, height: number, scale?: number, bitrate?: number }} opts\r\n * width/height: size of the frames you will add (before scaling)\r\n */\r\nexport async function createVideoEncoder(opts) {\r\n const scale = Math.max(1, Math.round(opts.scale || 1));\r\n const W = even(opts.width * scale), H = even(opts.height * scale);\r\n const fps = opts.fps || 30;\r\n const bitrate = opts.bitrate || Math.min(20e6, Math.max(2e6, W * H * fps * 0.15));\r\n if (typeof VideoEncoder === 'undefined') throw new Error('This browser has no WebCodecs VideoEncoder. Export a GIF or PNG sequence instead.');\r\n\r\n let codec = null, muxCodec = null;\r\n const list = opts.format === 'mp4' ? AVC.map(c => [c, 'avc']) : VP;\r\n for (const [c, m] of list) {\r\n const ok = await VideoEncoder.isConfigSupported({ codec: c, width: W, height: H, bitrate, framerate: fps }).catch(() => ({}));\r\n if (ok.supported) { codec = c; muxCodec = m; break; }\r\n }\r\n if (!codec) throw new Error(`No ${opts.format.toUpperCase()} encoder available in this browser at ${W}\u00D7${H}.`);\r\n\r\n const muxer = opts.format === 'mp4'\r\n ? createMp4Muxer({ width: W, height: H })\r\n : createWebmMuxer({ width: W, height: H, codec: muxCodec, fps });\r\n\r\n let error = null;\r\n const encoder = new VideoEncoder({\r\n output: (chunk, meta) => muxer.addChunk(chunk, meta),\r\n error: e => { error = e; },\r\n });\r\n // avc.format 'avc': length-prefixed NAL units plus a decoder config record, which MP4 needs\r\n encoder.configure({ codec, width: W, height: H, bitrate, framerate: fps, latencyMode: 'quality', ...(opts.format === 'mp4' ? { avc: { format: 'avc' } } : {}) });\r\n\r\n const canvas = new OffscreenCanvas(W, H);\r\n const ctx = canvas.getContext('2d');\r\n let i = 0;\r\n const frameUs = 1e6 / fps;\r\n\r\n return {\r\n width: W, height: H, codec,\r\n /** @param {{ width, height, data }} img */\r\n async add(img) {\r\n if (error) throw error;\r\n const up = scale > 1 ? upscale(img, scale) : img;\r\n ctx.fillStyle = '#000'; ctx.fillRect(0, 0, W, H);\r\n ctx.putImageData(new ImageData(new Uint8ClampedArray(up.data), up.width, up.height), 0, 0);\r\n const frame = new VideoFrame(canvas, { timestamp: Math.round(i * frameUs), duration: Math.round(frameUs) });\r\n encoder.encode(frame, { keyFrame: i % Math.max(1, Math.round(fps * 2)) === 0 });\r\n frame.close();\r\n i++;\r\n if (encoder.encodeQueueSize > 8) await new Promise(r => setTimeout(r, 0));\r\n },\r\n /** @returns {Promise<Blob>} */\r\n async finish() {\r\n await encoder.flush();\r\n if (error) throw error;\r\n encoder.close();\r\n return new Blob([muxer.finish()], { type: opts.format === 'mp4' ? 'video/mp4' : 'video/webm' });\r\n },\r\n };\r\n}\r\n\r\n/** Convenience: encode an array of frames in one go. */\r\nexport async function encodeVideo(frames, opts) {\r\n const enc = await createVideoEncoder({ ...opts, width: frames[0].width, height: frames[0].height });\r\n for (const f of frames) await enc.add(f);\r\n return enc.finish();\r\n}\r\n"],
5
5
  "mappings": "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",
6
6
  "names": ["ENC", "Writer", "v", "s", "b", "a", "n", "r", "p", "box", "type", "fill", "w", "body", "head", "fullBox", "version", "flags", "MATRIX", "createMp4Muxer", "width", "height", "timescale", "samples", "description", "chunk", "meta", "d", "data", "key", "timestamp", "duration", "t", "us", "pts", "dts", "lastDur", "durs", "i", "offsets", "hasOffsets", "o", "mediaDur", "movieDur", "stts", "last", "ctts", "keys", "dataSize", "ftyp", "buildMoov", "chunkOffset", "tr", "md", "mi", "di", "st", "name", "c", "k", "moovSize", "offset", "moov", "out", "ENC", "size8", "n", "b", "v", "i", "idBytes", "id", "out", "concat", "parts", "p", "o", "el", "data", "uint", "bytes", "x", "str", "s", "float64", "master", "children", "ID", "CODEC_IDS", "createWebmMuxer", "width", "height", "codec", "fps", "frames", "codecPrivate", "chunk", "meta", "d", "key", "timestamp", "frameMs", "duration", "info", "tracks", "clusters", "cur", "f", "t", "head", "clusterBytes", "c", "seekHead", "posInfo", "posTracks", "posCues", "pos", "cuePoints", "cues", "header", "segment", "parseColor", "c", "s", "ch", "upscale", "img", "k", "opts", "w", "h", "out", "src", "sw", "y", "sy", "so", "o", "r", "g", "b", "parseColor", "a", "x", "sa", "oa", "wait", "el", "ev", "ms", "res", "rej", "t", "ok", "MEDIA_ERRORS", "mediaError", "video", "code", "err", "waitOrFail", "done", "onOk", "onErr", "videoReady", "timeout", "e", "seekTo", "video", "time", "presented", "res", "seeked", "wait", "estimateFps", "fallback", "videoReady", "was", "times", "done", "cb", "_", "meta", "deltas", "i", "d", "b", "fps", "r", "frameTimes", "duration", "start", "end", "maxFrames", "out", "n", "forEachFrame", "opts", "wasPaused", "AVC", "VP", "videoSupport", "width", "height", "out", "codec", "even", "n", "createVideoEncoder", "opts", "scale", "W", "H", "fps", "bitrate", "muxCodec", "list", "c", "m", "muxer", "createMp4Muxer", "createWebmMuxer", "error", "encoder", "chunk", "meta", "e", "canvas", "ctx", "i", "frameUs", "img", "up", "upscale", "frame", "r", "encodeVideo", "frames", "enc", "f"]
7
7
  }
package/lib/core/color.js CHANGED
@@ -1,69 +1,69 @@
1
- // Colour helpers shared by the CPU path, the GPU uniforms and the editor.
2
- // All channel values are sRGB in 0..1 unless a name says otherwise.
3
-
4
- /** Parse '#rgb', '#rrggbb', '#rrggbbaa', 'rrggbb' or [r,g,b] (0..255) into [r, g, b] bytes. */
5
- export function parseColor(c) {
6
- if (Array.isArray(c)) return [c[0] | 0, c[1] | 0, c[2] | 0];
7
- let s = String(c).trim().replace(/^#|^0x/i, '');
8
- if (s.length === 3 || s.length === 4) s = s.slice(0, 3).split('').map(ch => ch + ch).join('');
9
- if (!/^[0-9a-f]{6}([0-9a-f]{2})?$/i.test(s)) throw new Error(`Invalid colour "${c}"`);
10
- return [parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16)];
11
- }
12
-
13
- /** True when `c` parses as a colour. */
14
- export function isColor(c) {
15
- try { parseColor(c); return true; } catch { return false; }
16
- }
17
-
18
- /** [r, g, b] bytes → '#rrggbb'. */
19
- export function toHex(rgb) {
20
- return '#' + rgb.slice(0, 3).map(v => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0')).join('');
21
- }
22
-
23
- /** Normalise any colour to '#rrggbb' (lower case). */
24
- export const normHex = c => toHex(parseColor(c));
25
-
26
- const lin = c => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
27
-
28
- /** sRGB (0..1) → OKLab [L, a, b]. Same maths as the GLSL `oklab()` in gpu/shaders.js. */
29
- export function oklab(r, g, b) {
30
- r = lin(r); g = lin(g); b = lin(b);
31
- const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
32
- const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
33
- const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
34
- return [
35
- 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,
36
- 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,
37
- 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s,
38
- ];
39
- }
40
-
41
- /** Relative luminance-ish weight used by saturation (Rec. 709). */
42
- export const LUMA = [0.2126, 0.7152, 0.0722];
43
-
44
- /** Perceived lightness 0..1 of a hex colour; handy for picking text colours on swatches. */
45
- export function lightness(hex) {
46
- const [r, g, b] = parseColor(hex);
47
- return oklab(r / 255, g / 255, b / 255)[0];
48
- }
49
-
50
- /**
51
- * Average distance (sRGB 0..1) from each palette colour to its nearest neighbour.
52
- * Ordered dithering scales its threshold by this so `strength` means the same thing
53
- * for a 2-colour and a 64-colour palette.
54
- */
55
- export function paletteSpread(rgbs) {
56
- if (rgbs.length < 2) return 1;
57
- let sum = 0;
58
- for (let i = 0; i < rgbs.length; i++) {
59
- let best = Infinity;
60
- for (let j = 0; j < rgbs.length; j++) {
61
- if (i === j) continue;
62
- const dr = rgbs[i][0] - rgbs[j][0], dg = rgbs[i][1] - rgbs[j][1], db = rgbs[i][2] - rgbs[j][2];
63
- const d = Math.sqrt(dr * dr + dg * dg + db * db);
64
- if (d < best) best = d;
65
- }
66
- sum += best;
67
- }
68
- return Math.max(0.04, Math.min(1, sum / rgbs.length / 255));
69
- }
1
+ // Colour helpers shared by the CPU path, the GPU uniforms and the editor.
2
+ // All channel values are sRGB in 0..1 unless a name says otherwise.
3
+
4
+ /** Parse '#rgb', '#rrggbb', '#rrggbbaa', 'rrggbb' or [r,g,b] (0..255) into [r, g, b] bytes. */
5
+ export function parseColor(c) {
6
+ if (Array.isArray(c)) return [c[0] | 0, c[1] | 0, c[2] | 0];
7
+ let s = String(c).trim().replace(/^#|^0x/i, '');
8
+ if (s.length === 3 || s.length === 4) s = s.slice(0, 3).split('').map(ch => ch + ch).join('');
9
+ if (!/^[0-9a-f]{6}([0-9a-f]{2})?$/i.test(s)) throw new Error(`Invalid colour "${c}"`);
10
+ return [parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16)];
11
+ }
12
+
13
+ /** True when `c` parses as a colour. */
14
+ export function isColor(c) {
15
+ try { parseColor(c); return true; } catch { return false; }
16
+ }
17
+
18
+ /** [r, g, b] bytes → '#rrggbb'. */
19
+ export function toHex(rgb) {
20
+ return '#' + rgb.slice(0, 3).map(v => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0')).join('');
21
+ }
22
+
23
+ /** Normalise any colour to '#rrggbb' (lower case). */
24
+ export const normHex = c => toHex(parseColor(c));
25
+
26
+ const lin = c => (c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
27
+
28
+ /** sRGB (0..1) → OKLab [L, a, b]. Same maths as the GLSL `oklab()` in gpu/shaders.js. */
29
+ export function oklab(r, g, b) {
30
+ r = lin(r); g = lin(g); b = lin(b);
31
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
32
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
33
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
34
+ return [
35
+ 0.2104542553 * l + 0.7936177850 * m - 0.0040720468 * s,
36
+ 1.9779984951 * l - 2.4285922050 * m + 0.4505937099 * s,
37
+ 0.0259040371 * l + 0.7827717662 * m - 0.8086757660 * s,
38
+ ];
39
+ }
40
+
41
+ /** Relative luminance-ish weight used by saturation (Rec. 709). */
42
+ export const LUMA = [0.2126, 0.7152, 0.0722];
43
+
44
+ /** Perceived lightness 0..1 of a hex colour; handy for picking text colours on swatches. */
45
+ export function lightness(hex) {
46
+ const [r, g, b] = parseColor(hex);
47
+ return oklab(r / 255, g / 255, b / 255)[0];
48
+ }
49
+
50
+ /**
51
+ * Average distance (sRGB 0..1) from each palette colour to its nearest neighbour.
52
+ * Ordered dithering scales its threshold by this so `strength` means the same thing
53
+ * for a 2-colour and a 64-colour palette.
54
+ */
55
+ export function paletteSpread(rgbs) {
56
+ if (rgbs.length < 2) return 1;
57
+ let sum = 0;
58
+ for (let i = 0; i < rgbs.length; i++) {
59
+ let best = Infinity;
60
+ for (let j = 0; j < rgbs.length; j++) {
61
+ if (i === j) continue;
62
+ const dr = rgbs[i][0] - rgbs[j][0], dg = rgbs[i][1] - rgbs[j][1], db = rgbs[i][2] - rgbs[j][2];
63
+ const d = Math.sqrt(dr * dr + dg * dg + db * db);
64
+ if (d < best) best = d;
65
+ }
66
+ sum += best;
67
+ }
68
+ return Math.max(0.04, Math.min(1, sum / rgbs.length / 255));
69
+ }
@@ -1,21 +1,21 @@
1
- // The whole pipeline on the CPU. Used when WebGL 2 is unavailable, in Node, and as the
2
- // reference the GPU output is tested against.
3
-
4
- import { resample, adjust, alphaCut, quantizeOrdered, quantizeDiffusion, removeOrphans, smoothMajority, outline } from './stages.js';
5
-
6
- /**
7
- * @param {ReturnType<import('../plan.js').makePlan>} plan
8
- * @param {{ width: number, height: number, data: ArrayLike<number> }} src RGBA bytes, rows top-down
9
- * @returns {Uint8ClampedArray} RGBA bytes, plan.width × plan.height
10
- */
11
- export function runCPU(plan, src) {
12
- const { width: w, height: h, settings: s } = plan;
13
- const buf = resample(src, w, h, s.size.resample, plan.crop);
14
- adjust(buf, s.adjust);
15
- alphaCut(buf, s.alpha.threshold);
16
- const px = plan.kind === 'diffusion' ? quantizeDiffusion(buf, w, h, plan) : quantizeOrdered(buf, w, h, plan);
17
- if (s.cleanup.orphans) removeOrphans(px, w, h);
18
- if (s.cleanup.smooth) smoothMajority(px, w, h);
19
- outline(px, w, h, plan.outline);
20
- return px;
21
- }
1
+ // The whole pipeline on the CPU. Used when WebGL 2 is unavailable, in Node, and as the
2
+ // reference the GPU output is tested against.
3
+
4
+ import { resample, adjust, alphaCut, quantizeOrdered, quantizeDiffusion, removeOrphans, smoothMajority, outline } from './stages.js';
5
+
6
+ /**
7
+ * @param {ReturnType<import('../plan.js').makePlan>} plan
8
+ * @param {{ width: number, height: number, data: ArrayLike<number> }} src RGBA bytes, rows top-down
9
+ * @returns {Uint8ClampedArray} RGBA bytes, plan.width × plan.height
10
+ */
11
+ export function runCPU(plan, src) {
12
+ const { width: w, height: h, settings: s } = plan;
13
+ const buf = resample(src, w, h, s.size.resample, plan.crop);
14
+ adjust(buf, s.adjust);
15
+ alphaCut(buf, s.alpha.threshold);
16
+ const px = plan.kind === 'diffusion' ? quantizeDiffusion(buf, w, h, plan) : quantizeOrdered(buf, w, h, plan);
17
+ if (s.cleanup.orphans) removeOrphans(px, w, h);
18
+ if (s.cleanup.smooth) smoothMajority(px, w, h);
19
+ outline(px, w, h, plan.outline);
20
+ return px;
21
+ }