From 0ba1f0af176e1485484ab2db69b3928622328139 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 11 Oct 2026 09:51:48 +0000 Subject: [PATCH] JavaScript port decodes YAPF 2; VS Code and Figma open every file yapf.js: decodeTexture (container, yapf-sum checks, code books with context decoding, 16-bit / float planes, GPU block tiles), BC1-BC7 / BC6H block decoding and textureToRGBA for display. Matches the C library on 417 generated files (surfaces bit-exact) and survives fuzzing with clean YapfErrors; fixtures in bindings/js/test-data keep it checked. VS Code preview: pickers for mip, face, layer, frame and slice; storage, normal-map convention and version in the info bar. Figma import: textures converted to 8-bit, 8-bit images stay exact. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DVrVthWN8GsmgHGUhhsCYm --- bindings/js/README.md | 21 +- bindings/js/test-data/v2-animation-notes.yapf | Bin 0 -> 13362 bytes bindings/js/test-data/v2-bc3-packed.yapf | Bin 0 -> 5102 bytes bindings/js/test-data/v2-bc5-packed.yapf | Bin 0 -> 5563 bytes bindings/js/test-data/v2-bc6h-encoded.yapf | Bin 0 -> 1692 bytes .../js/test-data/v2-bc6h-random-blocks.yapf | Bin 0 -> 4716 bytes bindings/js/test-data/v2-bc7-encoded.yapf | Bin 0 -> 5620 bytes .../js/test-data/v2-bc7-random-blocks.yapf | Bin 0 -> 4716 bytes bindings/js/test-data/v2-cube-mips.yapf | Bin 0 -> 14818 bytes bindings/js/test-data/v2-expected.json | 137 ++++ .../js/test-data/v2-float-gray-entropy.yapf | Bin 0 -> 11260 bytes .../test-data/v2-half-gray-alpha-entropy.yapf | Bin 0 -> 5339 bytes bindings/js/test-data/v2-rgba8-entropy.yapf | Bin 0 -> 1765 bytes bindings/js/test-data/v2-rgba8.yapf | Bin 0 -> 97 bytes .../js/test-data/v2-u16-rgba-entropy.yapf | Bin 0 -> 3672 bytes bindings/js/test-data/v2-u16-volume.yapf | Bin 0 -> 5736 bytes bindings/js/test.js | 34 + bindings/js/yapf.js | 727 ++++++++++++++++- extensions/figma/README.md | 6 + extensions/figma/src/ui.src.html | 16 +- extensions/figma/ui.html | 743 +++++++++++++++++- extensions/vscode/README.md | 15 +- extensions/vscode/media/preview.js | 118 ++- 23 files changed, 1751 insertions(+), 66 deletions(-) create mode 100644 bindings/js/test-data/v2-animation-notes.yapf create mode 100644 bindings/js/test-data/v2-bc3-packed.yapf create mode 100644 bindings/js/test-data/v2-bc5-packed.yapf create mode 100644 bindings/js/test-data/v2-bc6h-encoded.yapf create mode 100644 bindings/js/test-data/v2-bc6h-random-blocks.yapf create mode 100644 bindings/js/test-data/v2-bc7-encoded.yapf create mode 100644 bindings/js/test-data/v2-bc7-random-blocks.yapf create mode 100644 bindings/js/test-data/v2-cube-mips.yapf create mode 100644 bindings/js/test-data/v2-expected.json create mode 100644 bindings/js/test-data/v2-float-gray-entropy.yapf create mode 100644 bindings/js/test-data/v2-half-gray-alpha-entropy.yapf create mode 100644 bindings/js/test-data/v2-rgba8-entropy.yapf create mode 100644 bindings/js/test-data/v2-rgba8.yapf create mode 100644 bindings/js/test-data/v2-u16-rgba-entropy.yapf create mode 100644 bindings/js/test-data/v2-u16-volume.yapf diff --git a/bindings/js/README.md b/bindings/js/README.md index 309c0c7..a121a1c 100644 --- a/bindings/js/README.md +++ b/bindings/js/README.md @@ -1,6 +1,6 @@ # yapf.js — YAPF in JavaScript -Encoder and decoder for YAPF v1 in one dependency-free file. Works in +Encoder (version 1) and decoder (versions 1 and 2) in one dependency-free file. Works in browsers, Node.js, web workers, Figma plugins and VS Code webviews, and produces exactly the same bytes as the C library. @@ -22,3 +22,22 @@ image decodes in about 150 ms in Node / Chrome (the C library: 6 ms). TypeScript types are in `yapf.d.ts`. Example: `node example.mjs ../../other/YAPF.YAPF` · tests: `node test.js`. + +## Textures (YAPF 2) + +```js +const tex = YAPF.decodeTexture(bytes); // any v1 / v2 file +// tex: { width, height, depth, layers, frames, faces, shape, mipLevels, +// payload, format, sample, channels, flags, channelMap, normal, +// primaries, transfer, frameUs, notes, surfaces: [...] } +const s = YAPF.surfaceIndex(tex, frame, layer, face, mip); +const view = YAPF.textureToRGBA(tex, s); // { width, height, rgba } for a canvas +``` + +`surfaces[s]` is a `Uint8Array` (8-bit pixels or GPU blocks, ready for +WebGL / WebGPU compressed textures), `Uint16Array` (16-bit, half) or +`Float32Array`. Checksums are verified (`{ verify: false }` skips them); +errors are `YAPF.YapfError` with a `code` (`corrupt`, `checksum`, +`truncated`, `not-yapf`, `version`, `unsupported`). `decode()` also reads +8-bit version 2 files. The decoder is checked against the C library on the +files in `test-data/`. diff --git a/bindings/js/test-data/v2-animation-notes.yapf b/bindings/js/test-data/v2-animation-notes.yapf new file mode 100644 index 0000000000000000000000000000000000000000..a058ba0b98bae59261660ed45e9d200acd0b4137 GIT binary patch literal 13362 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 literal 0 HcmV?d00001 diff --git a/bindings/js/test-data/v2-bc3-packed.yapf b/bindings/js/test-data/v2-bc3-packed.yapf new file mode 100644 index 0000000000000000000000000000000000000000..3ea3f1d1db813bbe0c37e3535d628b986aec0445 GIT binary patch literal 5102 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 Zl4Q)Ol*Swhtco(qLC{`PjXgww{s#{lod^H` literal 0 HcmV?d00001 diff --git a/bindings/js/test-data/v2-bc6h-encoded.yapf b/bindings/js/test-data/v2-bc6h-encoded.yapf new file mode 100644 index 0000000000000000000000000000000000000000..24fdfbb22ee2bcbd9bef2300587789ea8cdfab95 GIT binary patch literal 1692 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 literal 0 HcmV?d00001 diff --git a/bindings/js/test-data/v2-bc7-encoded.yapf b/bindings/js/test-data/v2-bc7-encoded.yapf new file mode 100644 index 0000000000000000000000000000000000000000..da96f06f05cd1c1c72ba2486375ca96188f26596 GIT binary patch literal 5620 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 SI=&J&FGa6|x7bTHq5lD#lpiDj literal 0 HcmV?d00001 diff --git a/bindings/js/test-data/v2-expected.json b/bindings/js/test-data/v2-expected.json new file mode 100644 index 0000000..148768c --- /dev/null +++ b/bindings/js/test-data/v2-expected.json @@ -0,0 +1,137 @@ +{ + "v2-rgba8.yapf": { + "surfaces": [ + 480599072 + ], + "preview0": 480599072 + }, + "v2-float-gray-entropy.yapf": { + "surfaces": [ + 1656156314 + ], + "preview0": 2474033295 + }, + "v2-half-gray-alpha-entropy.yapf": { + "surfaces": [ + 2599040047 + ], + "preview0": 297007736 + }, + "v2-rgba8-entropy.yapf": { + "surfaces": [ + 554695116 + ], + "preview0": 554695116 + }, + "v2-u16-rgba-entropy.yapf": { + "surfaces": [ + 3416063949 + ], + "preview0": 1518500476 + }, + "v2-cube-mips.yapf": { + "surfaces": [ + 3098789294, + 2090642189, + 3178169037, + 3641419477, + 333399741, + 2333110333, + 1703812753, + 3098789294, + 2090642189, + 3178169037, + 3641419477, + 333399741, + 2333110333, + 1703812753, + 3098789294, + 2090642189, + 3178169037, + 3641419477, + 333399741, + 2333110333, + 1703812753, + 3098789294, + 2090642189, + 3178169037, + 3641419477, + 333399741, + 2333110333, + 1703812753, + 3098789294, + 2090642189, + 3178169037, + 3641419477, + 333399741, + 2333110333, + 1703812753, + 3098789294, + 2090642189, + 3178169037, + 3641419477, + 333399741, + 2333110333, + 1703812753 + ], + "preview0": 3098789294 + }, + "v2-u16-volume.yapf": { + "surfaces": [ + 1504297975, + 2857113671, + 3272367306, + 4248642449 + ], + "preview0": 3611091147 + }, + "v2-animation-notes.yapf": { + "surfaces": [ + 2496952478, + 2084186354, + 2971951804, + 4204487618 + ], + "preview0": 2496952478 + }, + "v2-bc6h-random-blocks.yapf": { + "surfaces": [ + 2051803746, + 562394057, + 2093502260 + ], + "preview0": 2386146506 + }, + "v2-bc7-random-blocks.yapf": { + "surfaces": [ + 3714498223, + 242396130, + 2835116443 + ], + "preview0": 4042788133 + }, + "v2-bc3-packed.yapf": { + "surfaces": [ + 1798067479 + ], + "preview0": 3027945807 + }, + "v2-bc5-packed.yapf": { + "surfaces": [ + 1681957115 + ], + "preview0": 1079658926 + }, + "v2-bc7-encoded.yapf": { + "surfaces": [ + 3415378546 + ], + "preview0": 851871445 + }, + "v2-bc6h-encoded.yapf": { + "surfaces": [ + 3372163902 + ], + "preview0": 89762387 + } +} \ No newline at end of file diff --git a/bindings/js/test-data/v2-float-gray-entropy.yapf b/bindings/js/test-data/v2-float-gray-entropy.yapf new file mode 100644 index 0000000000000000000000000000000000000000..5dfebbbb2e603ba8ef49fd6ae8befd80a04c676c GIT binary patch literal 11260 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 w4XOI;7Uv`dyQr);Dn4*3aghXJT+`h}TJRAN z@d2qQl|ia41RoJWe4(x^;@GXa3O?2}j8f2!wc8pnGrE8-weEk;y*Dp#O1r<|o^#Lt z{!jkzk4sf-3{4{nAsVR=LNnnHUf_>H5?H;dMb4cJMMcxhXZ5RDULYUNucvj0e0>ob z4rd!E+FP~X@1Y9c~pZ9vJ~iN&PT>EbgoGIS_|{6p~kC1jw`^}$?j+FzpgbF$iADM=(0!cJI?6uoscyfFL$UWGk>1j{1V_7yuHN;DLI>3Nb- z;%SqG7rZLmUhqDYlIX*sx{S9wT7(!MA2(@_Elc@4!D>KS{3yg5+&}{mGU5j&wuD=e z57&N%MJv4wYV}@~V&lCe*krHP)o#i2rBT7J?@8Q}0*PqSnlx!m)bG|5(?V;KFNMyH zk!V?oh`Ppg80+NXuMH+GdBOvS;YqztdL4Mm;}mE$?x#?m!NAJ$d)LB>JiA5obKN46 zboA;LoHi{+?5m-@w_{fbuh@AfXb?FYj#x8-o|Xj|fsau@6Q>_Ols+0R#mgU?>~(AG zX0HQ-Z=NsZkIh2rr`r%o*@L}D<6$yhf3&O?8tG)Ez& zWuLUq2%NJ!n(T+1O#i(M_a$@S#I+HuOstZL%h(g$+PC5d4*&^9sk0Xs^l8ZwTYoLtCPGBI& z3%fy#mj*yL@q&zg+zxT+VnxmZgE&p(L@UlVe0Kx^rx$D-5S$oAE(JKkcLgl$1h}Bf z6R#k{v2iS(!7Cszi9>Qa$L6_r3MsHnC-Lt_KZv|<7RA$C@lTod>Q39Tf;k19?H~F( zs>0q5JKdIRUAm`Qr#rIePHLB!Vvp^&*Cw=N559f= z@!cbtdFg8wo=Um(?ufwX)JYd_I4WA7x3=h_zNypLo_x5fbk)H-9j8(>KL6cuBXjw~ z%BOmt%*Dq}ESFy@7imEMeTJpc9P7hM&eT4Fxn)*A?;VKQT-cS~y!@;1s;mmL zI^TBZ0(|wKb$HfgVgk;^GXpaualTT&q&o(Jt3H_xeW&{NoO|}*Fh5|Nd)A<1-tN4K zO}NPhFLPEOdjXvRcJ+$JlCZjugF^^ae($UfZe?A<-y@nLZnv-Hq`~+5dJp+W>|YDX zO%wYme~5|UE|)i-)*M|Nmb5l#!9qgTa%p;@5Wg(q%?d0|3QpwjMyghFH-=+3zou4N zRq!PeOq+%e^AwgXb+^2ER?zejff&71f0>+Wwd`!z3z3m5)0*Kpn@{l(n>(wVEh3;( zTBNCnF%75q^lV1~fVN)PPUu~EIjfYX4#pOn{%LU+B=*FwFVSGyGDhyVc~<#&r(-OM zgoGd(S@sa1bR~FI%Kak21=PtjJQ8BmjAim6$hz?u0du5P7K`P**ReHLX$1iNZY7A| z03%bya9g!$SV|!sbBq*+xMeM|q>#M5iWno@(QilF4(ix2l#_fcvU=!Xf)O!jUaRD)8Gs1jwf-0^o0JF@9 zhZxxipb9gL8yqAY0`EKvIK&#_0-&6Tjsyn$H<}wQf-!DhKoTLnCI_(vRp3l|j0ZRZ zG#3P%ws4qaEa3QD3UI_v4D19f;z_`hFk*NGN;nrr44CwoP1rm`Y?GnJS08H$?;)Hbv* z_vqGGG0fKLKff1#vu4xo&iqhaV@c~5a_H~P=W6P@e%Q96J}MZ{WQN2?CHsdX>YD}t z>9~JI+@5JplbzZP5ci%?9%5M*UBnfYgaO!9W!3aCa>(vU5iG z_oVP6LabVsElda)c5|H~cq7A=Bp;S{hv!m>+Ym{$DtLEdRhbYI+*g?U1hJ@iE7BZ> zj5cl{L9Y)&GM@a7l UP6JWmlxw<{9 literal 0 HcmV?d00001 diff --git a/bindings/js/test-data/v2-rgba8-entropy.yapf b/bindings/js/test-data/v2-rgba8-entropy.yapf new file mode 100644 index 0000000000000000000000000000000000000000..c7092573a4577c3d790610e3cb0eb2beb5f38ebd GIT binary patch literal 1765 zcmeHHO=}cE5UuW>UU$ZTn|1W4NpNNSK=K2`%r345y^P{Pg@}JZa*^mooRtK^8Z>GI zkK@NdZ;D_Jfv`x>gLv_z1dJZMsRRTS4C3og7eOK5O`C0ItE>CftE$(1te)-T0WnTQ zb<815rYEBS-(SbOl3CgDqK7Va&U7-n;X2l*o*!RXkHaC>7Bph_Y0+)`jsJpJ&@Tk> zQbVwSB(h*s0%<1b3qw+?9IHO?8~j1A0WV>A2{2-8k0gq&mF5O<=H8P}d$)!sa%RU> z{_x!5?C7^gqf4JpEzN&eoOR~pNaKBV4!`l2wS`?*c(cVbielL=D9*K&$|60at%)p7 z$XIJrmT0Sw$u^g8C((KDopIhG1yp3L3kDe01_O*R4FimzJ1uaW2Y^E8tVmY8H$wE|pAsl_6?akMla4=aak6f{y==l_Z&gW4Tf literal 0 HcmV?d00001 diff --git a/bindings/js/test-data/v2-rgba8.yapf b/bindings/js/test-data/v2-rgba8.yapf new file mode 100644 index 0000000000000000000000000000000000000000..1fec3ffe0030d7915753af9e47e6bf7745c7c4c2 GIT binary patch literal 97 zcma!a3~*y&VPIrpU|?WG!5|(cNQ4TVVsHPn?6eR=0F*BXq|FK#et@{FK-}jMYQry4 Hd)6EPQ|bzU literal 0 HcmV?d00001 diff --git a/bindings/js/test-data/v2-u16-rgba-entropy.yapf b/bindings/js/test-data/v2-u16-rgba-entropy.yapf new file mode 100644 index 0000000000000000000000000000000000000000..334e8d8332b1dc657ea236037435323a3054c5fa GIT binary patch literal 3672 zcmeHJUu;uV7(e&i-rJ?iq=izjiLj9@>9CLyicgx|E!~h2lXmWoiCF|^W@9K|B6}f9 zOVGgBgolYlVsbM!LoPhbpq(-anBh!}CIeZNRPceIXq<^AOpW6FednIrZXG5(@M7eq z_nhy1-|u{Xe&6Y7pQ=wZaSu}%KJ|>TI=oomRhWjk^l5*<(R|W2@%@L-cdbuIKpFJI z4_$xe7-wt|bQcr|a~^fh>K-gqrg*AUm2tg6Q9VpU6$;aJc*eW}@oVPrVgbL=V<|{N+XSe3DIv-c6xvM-&H_czy>WXI# zS9OPNT_?7^tV_Y+`S<6JHr1=}D(>Yt{%S5YT1iqR&0C)j2eXVp51WqD(Eb0OnlCkGVL zUsuRIJ4D3@uTfc}=6laAJd$WHC@*_N4ybi+BZ?;9oXV61`JxUla8-Lco$tzhvF+HE zTzajL^0}9rj;{&h(|2)c-^J8l;qg@Ocxox>w)W<>#+o`~uaRYGQ|DTO9T$tf@zh#a zn#Rad(HZNK5Wu0?1`oYt?u=o}v{inFosq*SuJwR3VmKt|rqtKDg<4yZvNcBbSFc+Y z-&i2>ztVbfp6N_)f%R)QT9yY?GQ+Dq<5XHesj}TS!e7 zRuS7n`(QDSXZ0h6jn=zRyB^mGV>kuSSVLjsL>B@`R8$z%KJ2&tC?d{Za^fLWX3%c7 z2b*nr2kjpZ4D4Ug6X}Tz>>v2u7ibJ7_d{%mKOBc#1$9|6mLwULcryM3iNFu|lh2Ww z_BI5QA(ClxFnM9wOZ7{PCC-qYG@MkZ%yLnsDEwHlq~VlRpys;;#Dhc4Miu-x)Reod zptiQ6dX?l2_|K_)iOLm8hi^B~TXk?JSV6Rn4&fB2ox0!z5_}Y>`N(AjCAPV&1R5{L zB`YX6~~p-2VRZ3ti%J)&SuwpbMNY#+!-$QQiEjS<;64)7W*2@WXx-3KZMIZzfN+)?gjA6Gi38N2c!1g*m5rvZ9gM`Gt|E1Nj~}hu zhrX}qsqUfflIZ(|ZdQ8U=CkfnDbBiTan0;JwELUu%YMVHQfAZ)1u+lxw_NSVC2IAz z_!zE~=9(dBBO3IW`&*i`5`}A&C7NsC#HhJZCk42`1q2kv`yT)2JVxD&*Y>HLm-?%Ujy5T^4a{F%;AtNxJll(Q!%XR-|c>&yI=#eL@l dDxbTZBjFVxoP$uuIg$uOVimLjDI$vHmP6#G9DzneP_YssMD zpaDQ2H2?sz@Q{E90`Af<{@)g?_&4XnupPR-JEUP6^*5a;2LRTTi{60vn|_D{pl#bV z(~F-KM8c?T_SS{_!81P7BJRU0oZ9FnhAXs1A0*{^f`tIw9 zSX3>70bficvha>92_(LvjRy+@QJWwQkXiu*?;#uVK$-v|weq+;SQr*RivaKj4q)Re z0I#i$*G2+uX%xT*0&jtcPZ6~8AwC&^v^D^(%h$kTf+-`ahB2Y$lm1m0Pxo9bU8jd- z^@Cu*n`j8MaK#m8>3bnoJQj_xAp(Hh2&{FAI7QkC4wfL;AYRS|Uol7mBoYw8&0ECt z@b-qChn*rD0j6`iQ{8}RFm{uMSXZuH-5^g0`XzWnbi|8`KV zf9HRF{?(WRB0_8p#r(<+tTp=hKYZ3d0$5Cpz0Viwf7#*~dcg57Z1}eRGG)R`0xSnbS*F+1LI zQz48u+9jjb<(w*>&s4ye^xA#%B^bq$XO=2q91J;hms9`w-$O;o0uIL%%yL8AbV!uuh@CKi8D5}uy;LAZ738}D5=KAzmQf{gnv zZf!+46Y>mm z(hH{qx66N+J|T$Pu~T~P(>(3qfI|87&JOeWW1$wNklyz%;Cbtv4X1% z_0jgLt9xE_3$K-XT)Q;3EHq<8)Y6uPgR9$isK;I3?iDnza=7EtXuF+g^|e}O#EFW(j~j`)&$4wx(ppPYI>wR+>}TEWYv_OrgS)N?YJ z^Z9OJQKH}R!FCTAZjLeR%;2grrtU!rA-19NX21-@8CG)09aVgRvD6)y>whTO$cc1} zz}Pk^qjX;(4OZ)v^Yuua_CSdRuSjoOZ z!r&cUhz8Z{^^W0N_u)uZeecLcd#U;gK_w{d%P*s% zZJZt4q^iPXG&bI}?Wr0=axL>?eQ4G0 zcihfZ4VAXIAa6Jm#Xz<)Za7LuX=mvypEos|B=ekV?Iqo3`xv{N=Cn)&O_Yf-EEAwTPC)f zaqz+;uKNe`Tq{G$Vyt6@`3O8-0nS&?++fpUv{PZ(UrX~RI&RITcU}-JKaE>^9l3`8 zVeG6=cjeM8rD#KfE`9FT#M7^yS*o^OP3e|1huj;Or>5Oq4ruG_P8(}BSAfo3d36nS zq7V2Ia?iQRoJvQrZ+j$mr`qD~P44dr*cs5%Twb7N>;H(L2#zn*Rwqhs7iMY-qAtmaQ0R(kL~R3DVqd{aJpL7;B?(syO{U- zhBeLyx{cz{IXY(&8TEO$?=*yX){J8IGgOE{8JB5Yj_AQEm4oBwEXiX{5E?EFK*S4cfvBvr0R{SdCG*a zN&0xZoU0Tge*%*ALF4iAVh9zL*dF)j!H8``gA3EIXC)7BR9`&pzUyQ3NABQso)Qj8 zB@%v$0$HlJI7J=`gI#hgpSF00T4V>65T>26pqadK%fkuy{E&{S#7QUj167$MOIeDY z`M8AE@fp@&!$c82=Z!}!{`~DO;JArgdWyBQ>n<*5A7}i?Xr>jL|2mRS=!Om`*JpMW z-(0y6Y1Qi!tk-Q6bP#J(y}J}0#rBYoGH4pJXv3Z3(LMH#5K#rppy#@~-4v4(`wdE2 zk2oPJxyWLrhA=p<`1YX?S zXm-5dp(mKl%5G)22jw~v`%YZgkwms&v2lSLqz$~@o%l)HZXmjcSiv@Flut-5dV*@? z#!H)0o%`JChcV7yUZYHqZ#5Dd`*ise{OO#gjyws}INa>$v=+wO;2ZS;g)xU!X@FGnZY*;&yF2EcKgl3O4O;N z$5yKTkoazRUg6Ishletz=TPlwd6)h-D_SDDv86sGUsjXZf; zxBYDQ%iof&&A%DhVD8nSEF60CF6s~F!ScAc0TFL;blX5GWx&NNdibLEwbAx%g^@F; zmM+8TrFq%%Yd20^Qoqz%7*SX2|MT&m+sdD){!&<=zpTyCHF$WnRMudrNBzB`xb?KZ?PBCuNeybDIpT zSYg`KAm`@f>$+-gFPP?C_!-l^M-EMAO0%k!0uP>H8K_CRm$kIIkI_<+#bndteGHD@ zNtc)WoO?V3hOLBCu|hceKX;mqEC_)%Pa&J-0hIP#s4^o5NJ=*(XL%Xt+@*i()9az@ zbfq*tx+StMmp|v1_XuNM7U-6$_os70K$yUG^nlRO)3~}>T5^a%NbR`b>HFxG8zZD3 zUrU}EmfCmZXi%)sI*e=+WnvX>Bea?5%J+E5!v*_?-D|MhLv0~aFTt;yqBJ6QGCQQO zn%{-L=rtZ!qxDa1q(-%4MI+qa#!B! zsFH&XhX2YF3W-bs!TImsy`Ng{SX_?lG@}Z`x&8TLE zeTs-_fBIzM@Znu?e;xa@`29*@CutFWG!G5ROXN99W+Zw^!$|J5hAW(Nl;eDGu`rh zlXw1@DvjNUmvAk{iysnSZt{{PA$ip_xw}#-(&9FBcIH)Em$LXgwFF~G(Rw>cCH!8g zNRNH-rECgeTbGhvg}AwGE|qhU!?p(_YJP1=!LdXIl`l7F z2Cy4xX$^vT)JC&nVyLqu*5ynt{<;XKr=f|mxt-mii|y+Wx)&0`s6N80=pvE5DmL<`(lKt`@lX~cfLtA>dR zZtk@b&uLEjQn{9ntl1=!m(`J3^mgkWDz212YhjQEbft}!g E05%-b^8f$< literal 0 HcmV?d00001 diff --git a/bindings/js/test.js b/bindings/js/test.js index ec37609..930ff58 100644 --- a/bindings/js/test.js +++ b/bindings/js/test.js @@ -53,5 +53,39 @@ for (let i = 0; i < 2000; i++) { } check(threwOnTruncation, 'truncated files are rejected'); +/* Version 2 fixtures written by the C library: every surface and the + * preview must match what C decodes (FNV-1a hashes in v2-expected.json). */ +const fnv = (u8) => { let h = 2166136261; for (const v of u8) h = Math.imul(h ^ v, 16777619) >>> 0; return h; }; +const dataDir = path.join(__dirname, 'test-data'); +const expected = JSON.parse(fs.readFileSync(path.join(dataDir, 'v2-expected.json'), 'utf8')); +for (const [name, want] of Object.entries(expected)) { + const bytes = fs.readFileSync(path.join(dataDir, name)); + let tex = null; + try { tex = YAPF.decodeTexture(bytes); } catch (e) { check(false, `${name}: ${e.message}`); continue; } + const got = tex.surfaces.map((s) => fnv(new Uint8Array(s.buffer, s.byteOffset, s.byteLength))); + check(JSON.stringify(got) === JSON.stringify(want.surfaces), `${name}: surfaces match the C decoder`); + if (want.preview0 !== null) { + const view = YAPF.textureToRGBA(tex, 0, 0); + /* float previews may differ by 1 in rounding; compare only exact kinds */ + if (tex.payload === 1 ? tex.format !== 0x47 && tex.format !== 0x48 : tex.sample < 2) + check(fnv(view.rgba) === want.preview0, `${name}: preview matches the C library`); + } + /* damage must be caught by the checksums */ + const broken = Uint8Array.from(bytes); + broken[broken.length - 3] ^= 0x10; + let code = null; + try { YAPF.decodeTexture(broken); } catch (e) { code = e.code; } + check(code === 'checksum', `${name}: a damaged byte is reported as a checksum error`); +} +{ /* v1 decode() reads 8-bit v2 files and names what it cannot read */ + const cube = YAPF.decode(fs.readFileSync(path.join(dataDir, 'v2-cube-mips.yapf'))); + check(cube.mipLevels === 7 && cube.width === 64, 'decode() reads face 0 of a v2 cube'); + let msg = ''; + try { YAPF.decode(fs.readFileSync(path.join(dataDir, 'v2-bc7-encoded.yapf'))); } catch (e) { msg = e.message; } + check(/decodeTexture/.test(msg), 'decode() points GPU-block files to decodeTexture'); + const yapfSum = (t) => YAPF.checksum(new TextEncoder().encode(t)); + check(yapfSum('') === 0xd7550ed0 && yapfSum('YAPF') === 0xc8cc41d4, 'yapf-sum test vectors'); +} + console.log(`${tests} tests, ${fails} failures`); process.exit(fails ? 1 : 0); diff --git a/bindings/js/yapf.js b/bindings/js/yapf.js index 194e1a7..fb487fe 100644 --- a/bindings/js/yapf.js +++ b/bindings/js/yapf.js @@ -1,5 +1,5 @@ /* - * yapf.js — YAPF v1 encoder / decoder in plain JavaScript. + * yapf.js — YAPF encoder (v1) and decoder (v1 and v2) in plain JavaScript. * * Copyright 2026 Nexoniarz — Apache License 2.0. * @@ -10,9 +10,14 @@ * const file = YAPF.encode(img); // image → Uint8Array * const rgba = YAPF.toRGBA(img); // any channel count → RGBA pixels * + * const tex = YAPF.decodeTexture(bytes); // any v1 / v2 file + * const view = YAPF.textureToRGBA(tex, surface); // { width, height, rgba } + * * An image is { width, height, channels (1–4), pixels: Uint8Array, * gpuFormat, flags, mipLevels, mips: [Uint8Array, …] }. - * The specification is the comment at the top of yapf.h. + * Version 2 textures (GPU blocks, 16-bit / float, cubes, arrays, volumes, + * animation) are decoded by decodeTexture; encoding writes version 1. + * The specification: yapf.h (v1) and docs/FORMAT.md (v2). */ (function (root, factory) { if (typeof module === 'object' && module.exports) module.exports = factory(); @@ -42,7 +47,676 @@ const filterValid = (f) => f <= F_UPAVG || (f >= F_UP && f < F_UP + 8); const upK = (f) => (f >= F_UP ? f - F_UP + 1 : 1); - function corrupt() { throw new Error('YAPF: corrupt or unsupported file'); } + function corrupt() { throw new YapfError('corrupt', 'corrupt file'); } + + /* ── Version 2: textures (decoding) ───────────────────────────────── */ + + const ENTROPY = 0x20, NOXFORM = 0x40, BLOCK_TILE = 16; + const HUFF_BITS = 11, HUFF_SIZE = 1 << HUFF_BITS, SLOTS = 48, CTXS = 7; + const NEED = { WIDE: 1, BLOCKS: 2, ENTROPY: 4, LAYOUT: 8, FRAMES: 16, ALL: 31 }; + const r16 = (b, p) => b[p] | (b[p + 1] << 8); + const r64 = (b, p) => r32(b, p) + r32(b, p + 4) * 4294967296; + + class YapfError extends Error { + constructor(code, msg) { super('YAPF: ' + msg); this.code = code; } + } + const fail = (code, msg) => { throw new YapfError(code, msg); }; + const bad = () => fail('corrupt', 'corrupt file'); + + /* yapf-sum: 64-bit lanes kept as [hi, lo] uint32 pairs. */ + const KH = 0x9E3779B9, KL = 0x7F4A7C15; + function mul64(ah, al, bh, bl) { /* (a · b) mod 2^64 */ + const a0 = al & 0xFFFF, a1 = al >>> 16, b0 = bl & 0xFFFF, b1 = bl >>> 16; + const p00 = a0 * b0, p01 = a0 * b1, p10 = a1 * b0, p11 = a1 * b1; + const mid = (p00 >>> 16) + (p01 & 0xFFFF) + (p10 & 0xFFFF); + const lo = ((mid & 0xFFFF) * 65536 + (p00 & 0xFFFF)) >>> 0; + const hi = (p11 + (p01 >>> 16) + (p10 >>> 16) + Math.floor(mid / 65536) + + Math.imul(ah, bl) + Math.imul(al, bh)) >>> 0; + return [hi, lo]; + } + function checksum(b, p, n) { + const lanes = []; + for (let i = 0; i < 4; i++) { + const [h, l] = mul64(0, i + 1, KH, KL); + lanes.push([(h ^ Math.floor(n / 4294967296)) >>> 0, (l ^ (n >>> 0)) >>> 0]); + } + const words = Math.ceil(n / 8); + for (let j = 0; j < words; j++) { + const q = p + j * 8; + let wl, wh; + if (q + 8 <= p + n) { wl = r32(b, q); wh = r32(b, q + 4); } + else { + const t = new Uint8Array(8); t.set(b.subarray(q, p + n)); + wl = r32(t, 0); wh = r32(t, 4); + } + const a = lanes[j & 3]; + const [h, l] = mul64((a[0] ^ wh) >>> 0, (a[1] ^ wl) >>> 0, KH, KL); + a[0] = h; a[1] = (l ^ h) >>> 0; /* a ^= a >> 32 */ + } + const rot = (x, r) => r === 32 ? [x[1], x[0]] + : [((x[0] << r) | (x[1] >>> (32 - r))) >>> 0, ((x[1] << r) | (x[0] >>> (32 - r))) >>> 0]; + const r1 = rot(lanes[1], 16), r2 = rot(lanes[2], 32), r3 = rot(lanes[3], 48 - 32); + const r3b = [r3[1], r3[0]]; /* rotl 48 = rotl 16 then swap */ + let hh = (lanes[0][0] ^ r1[0] ^ r2[0] ^ r3b[0]) >>> 0, hl = (lanes[0][1] ^ r1[1] ^ r2[1] ^ r3b[1]) >>> 0; + [hh, hl] = mul64(hh, hl, KH, KL); + return (hl ^ hh) >>> 0; + } + + /* Huffman code table → lut[off .. off + 2048) (length << 8 | symbol). */ + function huffTable(b, p, end, lut, off) { + if (p >= end) bad(); + const n = b[p++] + 1, nbytes = (n + 1) >> 1; + if (end - p < nbytes) bad(); + const len = new Uint8Array(n), count = new Uint32Array(HUFF_BITS + 2); + let kraft = 0; + for (let i = 0; i < n; i++) { + const l = (b[p + (i >> 1)] >> ((i & 1) * 4)) & 15; + if (l > HUFF_BITS) bad(); + len[i] = l; count[l]++; + if (l) kraft += HUFF_SIZE >> l; + } + if ((n & 1) && (b[p + (n >> 1)] >> 4)) bad(); + if (kraft === 0 || kraft > HUFF_SIZE) bad(); + p += nbytes; + const next = new Uint32Array(HUFF_BITS + 2); + let code = 0; + count[0] = 0; + for (let l = 1; l <= HUFF_BITS; l++) { code = (code + count[l - 1]) << 1; next[l] = code; } + for (let s = 0; s < n; s++) { + const l = len[s]; + if (!l) continue; + let c = next[l]++, r = 0; + for (let i = 0; i < l; i++) { r = (r << 1) | (c & 1); c >>= 1; } + for (let k = r; k < HUFF_SIZE; k += 1 << l) lut[off + k] = (l << 8) | s; + } + return p; + } + + /* Code book → per slot one Uint16Array of 7 × 2048 entries. */ + function readBook(b, p, len) { + const end = p + len; + if (len < 2) bad(); + const n = r16(b, p); + p += 2; + if (n === 0 || n > SLOTS * CTXS) bad(); + const book = new Array(SLOTS).fill(null), seen = new Uint8Array(SLOTS * CTXS); + for (let i = 0; i < n; i++) { + if (end - p < 3) bad(); + const slot = b[p], ctx = b[p + 1]; + if (slot >= SLOTS || ctx >= CTXS || seen[slot * CTXS + ctx]) bad(); + seen[slot * CTXS + ctx] = 1; + if (!book[slot]) book[slot] = new Uint16Array(CTXS * HUFF_SIZE); + p = huffTable(b, p + 2, end, book[slot], ctx * HUFF_SIZE); + } + if (p !== end) bad(); + return book; + } + + /* Context of a residual from the folded residuals left + above. */ + const ctxOf = (s) => (s > 0) + (s > 2) + (s > 6) + (s > 14) + (s > 30) + (s > 62); + + /* Entropy plane payload: filters, u16 length, context-coded residuals. */ + function huffPlane(b, p, end, z, tw, th, book, slot) { + const nb = (th + BAND - 1) >> 3; + if (!book || !book[slot] || end - p < nb + 2) bad(); + const filt = b.subarray(p, p + nb); + for (let i = 0; i < nb; i++) if (!filterValid(filt[i])) bad(); + p += nb; + const L = r16(b, p); + p += 2; + if (end - p < L) bad(); + const lut = book[slot], se = p + L; + let buf = 0, bits = 0, pos = p, used = 0, i = 0; + for (let y = 0; y < th; y++) + for (let x = 0; x < tw; x++, i++) { + while (bits <= 24) { buf |= (pos < se ? b[pos] : 0) << bits; pos++; bits += 8; } + const s = (x ? z[i - 1] : 0) + (y ? z[i - tw] : 0); + const e = lut[(ctxOf(s) << HUFF_BITS) | (buf & (HUFF_SIZE - 1))]; + if (!e) bad(); + const n = e >> 8; + buf >>>= n; bits -= n; used += n; + z[i] = e & 255; + } + if (Math.ceil(used / 8) !== L) bad(); + z.fill(0, tw * th, tw * th + GROUP); + return { p: se, filt }; + } + + /* Wide samples: byte planes, transforms at the sample width. */ + function decodeTileWide(b, p, end, out, imgW, tx, ty, tw, th, ch, sample, book) { + const sb = sample === 3 ? 4 : 2, bits = 8 * sb, isf = sample >= 2, np = ch * sb; + const half = bits === 32 ? 0x80000000 : 0x8000; + const mod = bits === 32 ? (x) => x >>> 0 : (x) => x & 0xFFFF; + const sar1 = (v) => mod((v >>> 1) | (v & half)); + if (end <= p) bad(); + const hdr = b[p++]; + if (hdr === STORED) { + if (end - p !== tw * th * ch * sb) bad(); + for (let y = 0; y < th; y++) { + const d = ((ty + y) * imgW + tx) * ch; + for (let i = 0; i < tw * ch; i++, p += sb) + out[d + i] = sb === 2 ? r16(b, p) : r32(b, p); + } + return; + } + if (hdr & 0x8F) bad(); + if ((hdr & NOXFORM) && (hdr & SUBGREEN)) bad(); + if (ch < 3 && (hdr & (NOXFORM | SUBGREEN))) bad(); + const xf = ch < 3 ? 2 : (hdr & NOXFORM) ? 2 : (hdr & SUBGREEN) ? 1 : 0; + const tsel = ch < 3 ? 0 : xf; + if (end - p < 2) bad(); + const cmask = r16(b, p); + p += 2; + if (cmask >> np) bad(); + let emask = 0; + if (hdr & ENTROPY) { + if (end - p < 2 || !book) bad(); + emask = r16(b, p); + p += 2; + if ((emask >> np) || (emask & cmask)) bad(); + } + const RL = TILE + 1, PL = TILE * RL; + const planes = new Uint8Array(np * PL), z = new Uint8Array(TILE_PX + GROUP); + for (let pl = 0; pl < np; pl++) { + if (!(cmask & (1 << pl))) continue; + if (p >= end) bad(); + planes.fill(b[p++], pl * PL, (pl + 1) * PL); + } + for (let pl = 0; pl < np; pl++) { + if (cmask & (1 << pl)) continue; + z.fill(0); + const r = (emask & (1 << pl)) ? huffPlane(b, p, end, z, tw, th, book, tsel * 16 + pl) + : unpackPlane(b, p, end, z, tw, th); + p = r.p; + const base = pl * PL; + for (let y = 0; y < th; y++) { + const f = r.filt[y >> 3], k = upK(f); + unfilterRow(f, planes, base + y * RL, + y ? planes : ZERO_ROW, y ? base + (y - 1) * RL : 0, + y >= k ? planes : ZERO_ROW, y >= k ? base + (y - k) * RL : 0, + z, y * tw, tw); + } + } + if (p !== end) bad(); + const v = [0, 0, 0, 0]; + for (let y = 0; y < th; y++) { + const d0 = ((ty + y) * imgW + tx) * ch, r0 = y * RL; + for (let x = 0; x < tw; x++) { + for (let c = 0; c < ch; c++) { + let a = 0; + for (let k = 0; k < sb; k++) a = a * 256 + planes[(c * sb + k) * PL + r0 + x]; + v[c] = a; + } + if (xf === 0) { + const o = mod(v[1] - half), q = mod(v[2] - half), t = mod(v[0] - sar1(q)); + const g = mod(q + t), bb = mod(t - sar1(o)); + v[0] = mod(bb + o); v[1] = g; v[2] = bb; + } else if (xf === 1) { + const g = v[0]; + v[0] = mod(v[1] + g - half); v[2] = mod(v[2] + g - half); v[1] = g; + } + for (let c = 0; c < ch; c++) { + let s = v[c]; + if (isf) s = (s & half) ? mod(s ^ half) : mod(~s); + out[d0 + x * ch + c] = s; + } + } + } + } + + /* GPU block tile: raw, or packed lane by lane with the book. */ + function decodeBlockTile(b, p, end, packed, out, o0, rowb, tbw, tbh, S, book) { + const n = tbw * tbh; + if (!packed) { + if (end - p !== n * S) bad(); + for (let r = 0; r < tbh; r++) out.set(b.subarray(p + r * tbw * S, p + (r + 1) * tbw * S), o0 + r * rowb); + return; + } + if (!book) bad(); + let buf = 0, bits = 0, pos = p, used = 0; + for (let k = 0; k < S; k++) { + const lut = book[k]; + if (!lut) bad(); + for (let i = 0; i < n; i++) { + while (bits <= 24) { buf |= (pos < end ? b[pos] : 0) << bits; pos++; bits += 8; } + const e = lut[buf & (HUFF_SIZE - 1)]; + if (!e) bad(); + const l = e >> 8; + buf >>>= l; bits -= l; used += l; + out[o0 + Math.floor(i / tbw) * rowb + (i % tbw) * S + k] = e & 255; + } + } + if (Math.ceil(used / 8) !== end - p) bad(); + } + + const BLOCK_FORMATS = { + 0x40: [4, 4, 8], 0x41: [4, 4, 16], 0x42: [4, 4, 16], 0x43: [4, 4, 8], 0x44: [4, 4, 8], + 0x45: [4, 4, 16], 0x46: [4, 4, 16], 0x47: [4, 4, 16], 0x48: [4, 4, 16], 0x49: [4, 4, 16], + 0x50: [4, 4, 8], 0x51: [4, 4, 8], 0x52: [4, 4, 16], 0x53: [4, 4, 8], 0x54: [4, 4, 8], + 0x55: [4, 4, 16], 0x56: [4, 4, 16] + }; + [[4, 4], [5, 4], [5, 5], [6, 5], [6, 6], [8, 5], [8, 6], [8, 8], [10, 5], [10, 6], [10, 8], + [10, 10], [12, 10], [12, 12]].forEach(([w, h], i) => { BLOCK_FORMATS[0x60 + i] = [w, h, 16]; }); + + function mipSize(t, m) { + return [Math.max(1, t.width >>> m), Math.max(1, t.height >>> m), + t.shape === 2 ? Math.max(1, t.depth >>> m) : 1]; + } + + function surfaceBytes(t, m) { + const [w, h, d] = mipSize(t, m); + if (t.payload === 1) { + const [bw, bh, S] = BLOCK_FORMATS[t.format]; + return Math.ceil(w / bw) * Math.ceil(h / bh) * d * S; + } + return w * h * d * t.channels * (t.sample === 0 ? 1 : t.sample === 3 ? 4 : 2); + } + + /* Decodes one surface (v2) into a typed array. */ + function decodeSurface(b, t, s, verify) { + const m = s % t.mipLevels, [w, h, d] = mipSize(t, m); + const off = t._off[s], size = t._size[s], blocks = t.payload === 1; + let tcx, tcy, nbx = 0, nby = 0, S = 0; + if (blocks) { + const f = BLOCK_FORMATS[t.format]; + nbx = Math.ceil(w / f[0]); nby = Math.ceil(h / f[1]); S = f[2]; + tcx = Math.ceil(nbx / BLOCK_TILE); tcy = Math.ceil(nby / BLOCK_TILE); + } else { + tcx = Math.ceil(w / TILE); tcy = Math.ceil(h / TILE); + } + const tiles = tcx * tcy * d; + if (size < 4) bad(); + const bl = r32(b, off); + if (bl > size - 4 || Math.floor((size - 4 - bl) / 8) < tiles) bad(); + const pre = 4 + bl; + if (verify && checksum(b, off, pre + tiles * 8) !== t._tcheck[s]) fail('checksum', 'damaged file: a checksum does not match'); + let book = null; + if (bl) { + if (!(t.needs & NEED.ENTROPY)) bad(); + book = readBook(b, off + 4, bl); + } + const table = off + pre, end = off + size; + let pos = table + tiles * 8; + const sizes = new Array(tiles), packed = new Array(tiles), starts = new Array(tiles); + for (let i = 0; i < tiles; i++) { + let sz = r32(b, table + i * 8); + packed[i] = sz >>> 31; + sz &= 0x7FFFFFFF; + if (packed[i] && (!blocks || !book)) bad(); + if (sz > end - pos) bad(); + starts[i] = pos; sizes[i] = sz; pos += sz; + } + if (pos !== end) bad(); + + const bytes = surfaceBytes(t, m); + let out, store; + if (blocks || t.sample === 0) out = store = new Uint8Array(bytes); + else if (t.sample === 3) { store = new Uint32Array(bytes / 4); out = new Float32Array(store.buffer); } + else out = store = new Uint16Array(bytes / 2); + for (let i = 0; i < tiles; i++) { + const p = starts[i], e = p + sizes[i]; + if (verify && checksum(b, p, sizes[i]) !== r32(b, table + i * 8 + 4)) + fail('checksum', 'damaged file: a checksum does not match'); + const row = Math.floor(i / tcx), z = Math.floor(row / tcy), ty = row % tcy, tx = i % tcx; + if (blocks) { + const bx0 = tx * BLOCK_TILE, by0 = ty * BLOCK_TILE, rowb = nbx * S; + decodeBlockTile(b, p, e, packed[i], store, z * nby * rowb + by0 * rowb + bx0 * S, rowb, + Math.min(BLOCK_TILE, nbx - bx0), Math.min(BLOCK_TILE, nby - by0), S, book); + } else { + const x0 = tx * TILE, y0 = ty * TILE, tw = Math.min(TILE, w - x0), th = Math.min(TILE, h - y0); + const slice = w * h * t.channels; + const view = store.subarray(z * slice, (z + 1) * slice); + if (t.sample === 0) decodeTile(b, p, e, view, w, x0, y0, tw, th, t.channels, true, book); + else decodeTileWide(b, p, e, view, w, x0, y0, tw, th, t.channels, t.sample, book); + } + } + return out; + } + + /* + * decodeTexture(bytes, { verify = true }) — any YAPF file (v1 or v2). + * Returns { version, width, height, depth, layers, frames, faces, shape, + * mipLevels, payload, format, sample, channels, flags, channelMap, + * linearMask, normal, primaries, transfer, mipFilter, alphaRef, + * loopCount, frameUs, notes: [{ tag, data }], surfaces: [...] }, where + * surfaces[s] is a Uint8Array (8-bit pixels, GPU blocks), Uint16Array + * (16-bit, half bits) or Float32Array, numbered + * s = ((frame · layers + layer) · faces + face) · mipLevels + mip. + * Errors are YapfError with .code 'not-yapf', 'version', 'unsupported', + * 'corrupt', 'checksum' or 'truncated'. + */ + function decodeTexture(input, opts) { + const b = input instanceof Uint8Array ? input : new Uint8Array(input); + const verify = !(opts && opts.verify === false); + if (b.length < 5 || b[0] !== 0x59 || b[1] !== 0x41 || b[2] !== 0x50 || b[3] !== 0x46) + fail('not-yapf', 'not a YAPF file'); + if (b[4] === 1) { + const img = decodeV1(b); + return { version: 1, width: img.width, height: img.height, depth: 1, layers: 1, frames: 1, + faces: 1, shape: 0, mipLevels: img.mipLevels, payload: 0, format: img.gpuFormat, + sample: 0, channels: img.channels, flags: img.flags, channelMap: [0, 0, 0, 0], + linearMask: 0, normal: 0, primaries: 0, transfer: 0, mipFilter: 0, alphaRef: 0, + loopCount: 0, frameUs: [], notes: [], surfaces: img.mips }; + } + if (b[4] !== 2) fail('version', 'format version ' + b[4] + ' is newer than this decoder'); + if (b.length < 64) fail('truncated', 'the file is truncated'); + if (verify && checksum(b, 0, 0x3C) !== r32(b, 0x3C)) fail('checksum', 'damaged file: a checksum does not match'); + const t = { + version: 2, channels: b[5], format: b[6], mipLevels: b[7], flags: b[8], payload: b[9], + sample: b[10], shape: b[11], width: r32(b, 0x0C), height: r32(b, 0x10), depth: r32(b, 0x14), + layers: r16(b, 0x18), frames: r16(b, 0x1A), needs: r32(b, 0x1C), + channelMap: Array.from(b.subarray(0x20, 0x24)), linearMask: b[0x24], normal: b[0x25], + primaries: b[0x26], transfer: b[0x27], mipFilter: b[0x28], alphaRef: b[0x29], + loopCount: r16(b, 0x2A), frameUs: [], notes: [] + }; + const notesSize = r32(b, 0x2C), fileSize = r64(b, 0x30); + if (t.needs & ~NEED.ALL) fail('unsupported', 'the file needs a feature this decoder does not have'); + t.faces = t.shape === 1 ? 6 : 1; + if (t.channels < 1 || t.channels > 4 || t.sample > 3 || t.payload > 1 || t.shape > 2 || + t.width < 1 || t.width > MAX_DIM || t.height < 1 || t.height > MAX_DIM || + !t.layers || !t.frames || t.mipLevels < 1 || t.mipLevels > MAX_MIPS) bad(); + if (t.shape === 2 ? (t.depth < 1 || t.depth > MAX_DIM || t.layers !== 1) : t.depth > 1) bad(); + if (t.shape !== 2) t.depth = 1; + if (t.shape === 1 && t.width !== t.height) bad(); + if (t.payload === 1 && !BLOCK_FORMATS[t.format]) bad(); + if ((t.payload === 0 && t.sample !== 0 && !(t.needs & NEED.WIDE)) || + (t.payload === 1 && !(t.needs & NEED.BLOCKS)) || + ((t.shape !== 0 || t.layers > 1) && !(t.needs & NEED.LAYOUT)) || + (t.frames > 1 && !(t.needs & NEED.FRAMES))) bad(); + const S = t.frames * t.layers * t.faces * t.mipLevels; + if (S > (1 << 24)) bad(); + const front = 64 + 16 * S + (t.frames > 1 ? 4 * t.frames : 0) + notesSize; + if (front > fileSize) bad(); + if (b.length < fileSize) fail('truncated', 'the file is truncated'); + if (verify && checksum(b, 64, front - 64) !== r32(b, 0x38)) fail('checksum', 'damaged file: a checksum does not match'); + let q = 64 + 16 * S; + for (let f = 0; t.frames > 1 && f < t.frames; f++, q += 4) t.frameUs.push(r32(b, q)); + const qe = q + notesSize; + while (q < qe) { + if (qe - q < 8) bad(); + const sz = r32(b, q + 4); + if (qe - q - 8 < sz) bad(); + t.notes.push({ tag: String.fromCharCode(b[q], b[q + 1], b[q + 2], b[q + 3]), data: b.slice(q + 8, q + 8 + sz) }); + q += 8 + sz; + } + t._off = []; t._size = []; t._tcheck = []; + for (let s = 0; s < S; s++) { + const e = 64 + 16 * s, off = r64(b, e), size = r32(b, e + 8); + if (off < front || off > fileSize || size > fileSize - off) bad(); + t._off.push(off); t._size.push(size); t._tcheck.push(r32(b, e + 12)); + } + t.surfaces = []; + for (let s = 0; s < S; s++) t.surfaces.push(decodeSurface(b, t, s, verify)); + delete t._off; delete t._size; delete t._tcheck; + return t; + } + + /* ── GPU block decoding (previews) ───────────────────────────────── */ + + const PART2 = [0xCCCC, 0x8888, 0xEEEE, 0xECC8, 0xC880, 0xFEEC, 0xFEC8, 0xEC80, 0xC800, 0xFFEC, 0xFE80, 0xE800, + 0xFFE8, 0xFF00, 0xFFF0, 0xF000, 0xF710, 0x008E, 0x7100, 0x08CE, 0x008C, 0x7310, 0x3100, 0x8CCE, 0x088C, 0x3110, + 0x6666, 0x366C, 0x17E8, 0x0FF0, 0x718E, 0x399C, 0xAAAA, 0xF0F0, 0x5A5A, 0x33CC, 0x3C3C, 0x55AA, 0x9696, 0xA55A, + 0x73CE, 0x13C8, 0x324C, 0x3BDC, 0x6996, 0xC33C, 0x9966, 0x0660, 0x0272, 0x04E4, 0x4E40, 0x2720, 0xC936, 0x936C, + 0x39C6, 0x639C, 0x9336, 0x9CC6, 0x817E, 0xE718, 0xCCF0, 0x0FCC, 0x7744, 0xEE22]; + const PART3 = [0xAA685050, 0x6A5A5040, 0x5A5A4200, 0x5450A0A8, 0xA5A50000, 0xA0A05050, 0x5555A0A0, 0x5A5A5050, + 0xAA550000, 0xAA555500, 0xAAAA5500, 0x90909090, 0x94949494, 0xA4A4A4A4, 0xA9A59450, 0x2A0A4250, 0xA5945040, + 0x0A425054, 0xA5A5A500, 0x55A0A0A0, 0xA8A85454, 0x6A6A4040, 0xA4A45000, 0x1A1A0500, 0x0050A4A4, 0xAAA59090, + 0x14696914, 0x69691400, 0xA08585A0, 0xAA821414, 0x50A4A450, 0x6A5A0200, 0xA9A58000, 0x5090A0A8, 0xA8A09050, + 0x24242424, 0x00AA5500, 0x24924924, 0x24499224, 0x50A50A50, 0x500AA550, 0xAAAA4444, 0x66660000, 0xA5A0A5A0, + 0x50A050A0, 0x69286928, 0x44AAAA44, 0x66666600, 0xAA444444, 0x54A854A8, 0x95809580, 0x96969600, 0xA85454A8, + 0x80959580, 0xAA141414, 0x96960000, 0xAAAA1414, 0xA05050A0, 0xA0A5A5A0, 0x96000000, 0x40804080, 0xA9A8A9A8, + 0xAAAAAA44, 0x2A4A5254]; + const ANCHOR2 = [15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 2, 8, 2, 2, 8, 8, 15, 2, 8, 2, 2, + 8, 8, 2, 2, 15, 15, 6, 8, 2, 8, 15, 15, 2, 8, 2, 2, 2, 15, 15, 6, 6, 2, 6, 8, 15, 15, 2, 2, 15, 15, 15, 15, 15, 2, 2, 15]; + const ANCHOR3A = [3, 3, 15, 15, 8, 3, 15, 15, 8, 8, 6, 6, 6, 5, 3, 3, 3, 3, 8, 15, 3, 3, 6, 10, 5, 8, 8, 6, 8, 5, 15, + 15, 8, 15, 3, 5, 6, 10, 8, 15, 15, 3, 15, 5, 15, 15, 15, 15, 3, 15, 5, 5, 5, 8, 5, 10, 5, 10, 8, 13, 15, 12, 3, 3]; + const ANCHOR3B = [15, 8, 8, 3, 15, 15, 3, 8, 15, 15, 15, 15, 15, 15, 15, 8, 15, 8, 15, 3, 15, 8, 15, 8, 3, 15, 6, 10, + 15, 15, 10, 8, 15, 3, 15, 10, 10, 8, 9, 10, 6, 15, 8, 15, 3, 6, 6, 8, 15, 3, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 3, 15, 15, 8]; + const WEIGHTS = { 2: [0, 21, 43, 64], 3: [0, 9, 18, 27, 37, 46, 55, 64], + 4: [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64] }; + const interp = (a, b, w) => (a * (64 - w) + b * w + 32) >> 6; + + function bits128(b, o) { + let pos = 0; + return (n) => { + let v = 0; + for (let i = 0; i < n; i++, pos++) v |= ((b[o + (pos >> 3)] >> (pos & 7)) & 1) << i; + return v >>> 0; + }; + } + + function rgb565(c) { + const r = (c >> 11) & 31, g = (c >> 5) & 63, bl = c & 31; + return [(r << 3) | (r >> 2), (g << 2) | (g >> 4), (bl << 3) | (bl >> 2)]; + } + + function bc1Block(b, o, out, fourOnly) { + const c0 = r16(b, o), c1 = r16(b, o + 2), idx = r32(b, o + 4), four = c0 > c1 || fourOnly; + const p0 = rgb565(c0), p1 = rgb565(c1), pal = [p0.concat(255), p1.concat(255), [0, 0, 0, 255], [0, 0, 0, 255]]; + for (let k = 0; k < 3; k++) { + if (four) { pal[2][k] = ((2 * p0[k] + p1[k] + 1) / 3) | 0; pal[3][k] = ((p0[k] + 2 * p1[k] + 1) / 3) | 0; } + else { pal[2][k] = (p0[k] + p1[k] + 1) >> 1; pal[3][k] = 0; } + } + if (!four) pal[3][3] = 0; + for (let i = 0; i < 16; i++) { const c = pal[(idx >>> (2 * i)) & 3]; for (let k = 0; k < 4; k++) out[i * 4 + k] = c[k]; } + } + + function bc4Block(b, o, out, ch, snorm) { + const pal = new Array(8); + let r0 = b[o], r1 = b[o + 1]; + if (snorm) { r0 = Math.max(-127, (r0 << 24) >> 24); r1 = Math.max(-127, (r1 << 24) >> 24); } + pal[0] = r0; pal[1] = r1; + const div = (v, d) => (v >= 0 ? Math.floor((v + (d >> 1)) / d) : -Math.floor((-v + (d >> 1)) / d)); + if (r0 > r1) for (let i = 2; i < 8; i++) pal[i] = div((8 - i) * r0 + (i - 1) * r1, 7); + else { for (let i = 2; i < 6; i++) pal[i] = div((6 - i) * r0 + (i - 1) * r1, 5); pal[6] = snorm ? -127 : 0; pal[7] = snorm ? 127 : 255; } + let lo = r32(b, o + 2), hi = r16(b, o + 6); + for (let i = 0; i < 16; i++) { + const bit = 3 * i, v = bit < 30 ? (lo >>> bit) & 7 : bit === 30 ? ((lo >>> 30) | ((hi & 1) << 2)) : (hi >> (bit - 32)) & 7; + out[i * 4 + ch] = pal[v] + (snorm ? 128 : 0); + } + } + + const BC7 = [[3, 4, 0, 0, 4, 0, 1, 0, 3, 0], [2, 6, 0, 0, 6, 0, 0, 1, 3, 0], [3, 6, 0, 0, 5, 0, 0, 0, 2, 0], + [2, 6, 0, 0, 7, 0, 1, 0, 2, 0], [1, 0, 2, 1, 5, 6, 0, 0, 2, 3], [1, 0, 2, 0, 7, 8, 0, 0, 2, 2], + [1, 0, 0, 0, 7, 7, 1, 0, 4, 0], [2, 6, 0, 0, 5, 5, 1, 0, 2, 0]]; + const subsetOf = (ns, part, i) => ns === 2 ? (PART2[part] >> i) & 1 : ns === 3 ? (PART3[part] >>> (2 * i)) & 3 : 0; + const isAnchor = (ns, part, i) => i === 0 || (ns === 2 && i === ANCHOR2[part]) || + (ns === 3 && (i === ANCHOR3A[part] || i === ANCHOR3B[part])); + + function bc7Block(b, o, out) { + const get = bits128(b, o); + let mode = 0; + while (mode < 8 && !get(1)) mode++; + if (mode === 8) { out.fill(0, 0, 64); return; } + const [ns, pb, rb, isbB, cb, ab, epb, spb, ib, ib2] = BC7[mode]; + const part = get(pb), rot = get(rb), isb = get(isbB), ne = ns * 2; + const ep = []; for (let e = 0; e < ne; e++) ep.push([0, 0, 0, 255]); + for (let c = 0; c < 3; c++) for (let e = 0; e < ne; e++) ep[e][c] = get(cb); + for (let e = 0; e < ne; e++) ep[e][3] = ab ? get(ab) : 255; + const pbit = epb | spb; + if (epb) for (let e = 0; e < ne; e++) { const p = get(1); for (let c = 0; c < 4; c++) if (c < 3 || ab) ep[e][c] = (ep[e][c] << 1) | p; } + else if (spb) for (let k = 0; k < ns; k++) { const p = get(1); for (let c = 0; c < 3; c++) { ep[2 * k][c] = (ep[2 * k][c] << 1) | p; ep[2 * k + 1][c] = (ep[2 * k + 1][c] << 1) | p; } } + for (let e = 0; e < ne; e++) for (let c = 0; c < 4; c++) { + if (c === 3 && !ab) continue; + const nb = (c < 3 ? cb : ab) + pbit, v = ep[e][c] << (8 - nb); + ep[e][c] = v | (v >> nb); + } + const idx = [], idx2 = []; + for (let i = 0; i < 16; i++) idx.push(get(ib - (isAnchor(ns, part, i) ? 1 : 0))); + for (let i = 0; i < 16; i++) idx2.push(ib2 ? get(ib2 - (i === 0 ? 1 : 0)) : 0); + const w1 = WEIGHTS[ib], w2 = WEIGHTS[ib2 || 2]; + for (let i = 0; i < 16; i++) { + const k = subsetOf(ns, part, i), A = ep[2 * k], B = ep[2 * k + 1]; + let cw, aw; + if (!ib2) cw = aw = w1[idx[i]]; + else if (!isb) { cw = w1[idx[i]]; aw = w2[idx2[i]]; } + else { cw = w2[idx2[i]]; aw = w1[idx[i]]; } + const px = [interp(A[0], B[0], cw), interp(A[1], B[1], cw), interp(A[2], B[2], cw), interp(A[3], B[3], aw)]; + if (rot) { const t = px[3]; px[3] = px[rot - 1]; px[rot - 1] = t; } + for (let c = 0; c < 4; c++) out[i * 4 + c] = px[c]; + } + } + + /* BC6H header layouts: [field, first bit, count]; fields 0–3 red w x y z, + * 4–7 green, 8–11 blue, 12 partition. */ + const RW = 0, RX = 1, RY = 2, RZ = 3, GW = 4, GX = 5, GY = 6, GZ = 7, BW = 8, BX = 9, BY = 10, BZ = 11, PD = 12; + const BC6 = [ + [0x00, 10, 5, 5, 5, [[GY,4,1],[BY,4,1],[BZ,4,1],[RW,0,10],[GW,0,10],[BW,0,10],[RX,0,5],[GZ,4,1],[GY,0,4],[GX,0,5],[BZ,0,1],[GZ,0,4],[BX,0,5],[BZ,1,1],[BY,0,4],[RY,0,5],[BZ,2,1],[RZ,0,5],[BZ,3,1],[PD,0,5]]], + [0x01, 7, 6, 6, 6, [[GY,5,1],[GZ,4,1],[GZ,5,1],[RW,0,7],[BZ,0,1],[BZ,1,1],[BY,4,1],[GW,0,7],[BY,5,1],[BZ,2,1],[GY,4,1],[BW,0,7],[BZ,3,1],[BZ,5,1],[BZ,4,1],[RX,0,6],[GY,0,4],[GX,0,6],[GZ,0,4],[BX,0,6],[BY,0,4],[RY,0,6],[RZ,0,6],[PD,0,5]]], + [0x02, 11, 5, 4, 4, [[RW,0,10],[GW,0,10],[BW,0,10],[RX,0,5],[RW,10,1],[GY,0,4],[GX,0,4],[GW,10,1],[BZ,0,1],[GZ,0,4],[BX,0,4],[BW,10,1],[BZ,1,1],[BY,0,4],[RY,0,5],[BZ,2,1],[RZ,0,5],[BZ,3,1],[PD,0,5]]], + [0x06, 11, 4, 5, 4, [[RW,0,10],[GW,0,10],[BW,0,10],[RX,0,4],[RW,10,1],[GZ,4,1],[GY,0,4],[GX,0,5],[GW,10,1],[GZ,0,4],[BX,0,4],[BW,10,1],[BZ,1,1],[BY,0,4],[RY,0,4],[BZ,0,1],[BZ,2,1],[RZ,0,4],[GY,4,1],[BZ,3,1],[PD,0,5]]], + [0x0A, 11, 4, 4, 5, [[RW,0,10],[GW,0,10],[BW,0,10],[RX,0,4],[RW,10,1],[BY,4,1],[GY,0,4],[GX,0,4],[GW,10,1],[BZ,0,1],[GZ,0,4],[BX,0,5],[BW,10,1],[BY,0,4],[RY,0,4],[BZ,1,1],[BZ,2,1],[RZ,0,4],[BZ,4,1],[BZ,3,1],[PD,0,5]]], + [0x0E, 9, 5, 5, 5, [[RW,0,9],[BY,4,1],[GW,0,9],[GY,4,1],[BW,0,9],[BZ,4,1],[RX,0,5],[GZ,4,1],[GY,0,4],[GX,0,5],[BZ,0,1],[GZ,0,4],[BX,0,5],[BZ,1,1],[BY,0,4],[RY,0,5],[BZ,2,1],[RZ,0,5],[BZ,3,1],[PD,0,5]]], + [0x12, 8, 6, 5, 5, [[RW,0,8],[GZ,4,1],[BY,4,1],[GW,0,8],[BZ,2,1],[GY,4,1],[BW,0,8],[BZ,3,1],[BZ,4,1],[RX,0,6],[GY,0,4],[GX,0,5],[BZ,0,1],[GZ,0,4],[BX,0,5],[BZ,1,1],[BY,0,4],[RY,0,6],[RZ,0,6],[PD,0,5]]], + [0x16, 8, 5, 6, 5, [[RW,0,8],[BZ,0,1],[BY,4,1],[GW,0,8],[GY,5,1],[GY,4,1],[BW,0,8],[GZ,5,1],[BZ,4,1],[RX,0,5],[GZ,4,1],[GY,0,4],[GX,0,6],[GZ,0,4],[BX,0,5],[BZ,1,1],[BY,0,4],[RY,0,5],[BZ,2,1],[RZ,0,5],[BZ,3,1],[PD,0,5]]], + [0x1A, 8, 5, 5, 6, [[RW,0,8],[BZ,1,1],[BY,4,1],[GW,0,8],[BY,5,1],[GY,4,1],[BW,0,8],[BZ,5,1],[BZ,4,1],[RX,0,5],[GZ,4,1],[GY,0,4],[GX,0,5],[BZ,0,1],[GZ,0,4],[BX,0,6],[BY,0,4],[RY,0,5],[BZ,2,1],[RZ,0,5],[BZ,3,1],[PD,0,5]]], + [0x1E, 6, 6, 6, 6, [[RW,0,6],[GZ,4,1],[BZ,0,1],[BZ,1,1],[BY,4,1],[GW,0,6],[GY,5,1],[BY,5,1],[BZ,2,1],[GY,4,1],[BW,0,6],[GZ,5,1],[BZ,3,1],[BZ,5,1],[BZ,4,1],[RX,0,6],[GY,0,4],[GX,0,6],[GZ,0,4],[BX,0,6],[BY,0,4],[RY,0,6],[RZ,0,6],[PD,0,5]]], + [0x03, 10, 10, 10, 10, [[RW,0,10],[GW,0,10],[BW,0,10],[RX,0,10],[GX,0,10],[BX,0,10]]], + [0x07, 11, 9, 9, 9, [[RW,0,10],[GW,0,10],[BW,0,10],[RX,0,9],[RW,10,1],[GX,0,9],[GW,10,1],[BX,0,9],[BW,10,1]]], + [0x0B, 12, 8, 8, 8, [[RW,0,10],[GW,0,10],[BW,0,10],[RX,0,8],[RW,11,1],[RW,10,1],[GX,0,8],[GW,11,1],[GW,10,1],[BX,0,8],[BW,11,1],[BW,10,1]]], + [0x0F, 16, 4, 4, 4, [[RW,0,10],[GW,0,10],[BW,0,10],[RX,0,4],[RW,15,1],[RW,14,1],[RW,13,1],[RW,12,1],[RW,11,1],[RW,10,1],[GX,0,4],[GW,15,1],[GW,14,1],[GW,13,1],[GW,12,1],[GW,11,1],[GW,10,1],[BX,0,4],[BW,15,1],[BW,14,1],[BW,13,1],[BW,12,1],[BW,11,1],[BW,10,1]]] + ]; + const sext = (v, n) => { const m = 1 << (n - 1); v &= (1 << n) - 1; return (v ^ m) - m; }; + function bc6Unq(v, n, sgn) { + if (!sgn) { + if (n >= 15) return v; + if (v === 0) return 0; + if (v === (1 << n) - 1) return 0xFFFF; + return ((v << 16) + 0x8000) >> n; + } + if (n >= 16) return v; + const s = v < 0; if (s) v = -v; + const u = v === 0 ? 0 : v >= (1 << (n - 1)) - 1 ? 0x7FFF : ((v << 15) + 0x4000) >> (n - 1); + return s ? -u : u; + } + function bc6Finish(v, sgn) { + if (!sgn) return (v * 31) >> 6; + v = v < 0 ? -(((-v) * 31) >> 5) : (v * 31) >> 5; + return v < 0 ? 0x8000 | -v : v; + } + function bc6Block(b, o, out, sgn) { + const get = bits128(b, o); + let code = get(2); + if (code > 1) code |= get(3) << 2; + const m = BC6.findIndex((x) => x[0] === code); + if (m < 0) { out.fill(0, 0, 48); return; } + const [, W, dr, dg, db, seg] = BC6[m], f = new Array(13).fill(0); + for (const [fi, lo, n] of seg) f[fi] |= get(n) << lo; + const regions = m < 10 ? 2 : 1, ne = regions * 2, dbits = [dr, dg, db], ep = [[], [], [], []]; + for (let c = 0; c < 3; c++) { + for (let e = 0; e < ne; e++) ep[e][c] = f[c * 4 + e]; + if (sgn) ep[0][c] = sext(ep[0][c], W); + const delta = m !== 9 && m !== 10; + for (let e = 1; e < ne; e++) { + if (delta || sgn) ep[e][c] = sext(ep[e][c], dbits[c]); + if (delta) { ep[e][c] = (ep[e][c] + ep[0][c]) & ((1 << W) - 1); if (sgn) ep[e][c] = sext(ep[e][c], W); } + } + for (let e = 0; e < ne; e++) ep[e][c] = bc6Unq(ep[e][c], W, sgn); + } + const part = f[PD], ib = regions === 2 ? 3 : 4, wt = WEIGHTS[ib]; + for (let i = 0; i < 16; i++) { + const k = regions === 2 ? (PART2[part] >> i) & 1 : 0; + const anchor = i === 0 || (regions === 2 && i === ANCHOR2[part]); + const ix = get(ib - (anchor ? 1 : 0)); + for (let c = 0; c < 3; c++) out[i * 3 + c] = bc6Finish(interp(ep[2 * k][c], ep[2 * k + 1][c], wt[ix]), sgn); + } + } + + /* decodeBlocks(format, blocks, w, h) → RGBA Uint8Array (LDR) or RGB + * half-bits Uint16Array (BC6H); null for ETC2 / EAC / ASTC. */ + function decodeBlocks(format, blocks, w, h) { + if (format >= 0x50 || !BLOCK_FORMATS[format]) return null; + const S = BLOCK_FORMATS[format][2], nbx = Math.ceil(w / 4), nby = Math.ceil(h / 4); + const hdr = format === 0x47 || format === 0x48; + const out = hdr ? new Uint16Array(w * h * 3) : new Uint8Array(w * h * 4); + const px = new Int32Array(64), hx = new Int32Array(48); + let o = 0; + for (let by = 0; by < nby; by++) + for (let bx = 0; bx < nbx; bx++, o += S) { + switch (format) { + case 0x40: bc1Block(blocks, o, px, false); break; + case 0x41: + bc1Block(blocks, o + 8, px, true); + for (let i = 0; i < 16; i++) px[i * 4 + 3] = ((blocks[o + (i >> 1)] >> (4 * (i & 1))) & 15) * 17; + break; + case 0x42: bc1Block(blocks, o + 8, px, true); bc4Block(blocks, o, px, 3, false); break; + case 0x43: case 0x44: + px.fill(0); bc4Block(blocks, o, px, 0, format === 0x44); + for (let i = 0; i < 16; i++) px[i * 4 + 3] = 255; + break; + case 0x45: case 0x46: + px.fill(0); bc4Block(blocks, o, px, 0, format === 0x46); bc4Block(blocks, o + 8, px, 1, format === 0x46); + for (let i = 0; i < 16; i++) px[i * 4 + 3] = 255; + break; + case 0x49: bc7Block(blocks, o, px); break; + default: bc6Block(blocks, o, hx, format === 0x48); + } + for (let y = 0; y < 4 && by * 4 + y < h; y++) + for (let x = 0; x < 4 && bx * 4 + x < w; x++) { + const d = (by * 4 + y) * w + bx * 4 + x, i = y * 4 + x; + if (hdr) for (let c = 0; c < 3; c++) out[d * 3 + c] = hx[i * 3 + c]; + else for (let c = 0; c < 4; c++) out[d * 4 + c] = px[i * 4 + c]; + } + } + return out; + } + + function halfToFloat(h) { + const s = h & 0x8000 ? -1 : 1, e = (h >> 10) & 31, m = h & 0x3FF; + if (e === 0) return s * m * Math.pow(2, -24); + if (e === 31) return m ? NaN : s * Infinity; + return s * (1 + m / 1024) * Math.pow(2, e - 15); + } + + const linToSrgb = (v) => !(v > 0) ? 0 : v >= 1 ? 255 + : Math.round(255 * (v <= 0.0031308 ? v * 12.92 : 1.055 * Math.pow(v, 1 / 2.4) - 0.055)); + + /* + * textureToRGBA(tex, surface = 0, slice = 0) → { width, height, + * rgba: Uint8ClampedArray } for display: gray expanded, 16-bit rounded, + * half / float clamped and sRGB encoded, GPU blocks decoded. Throws for + * ETC2 / EAC / ASTC. + */ + function textureToRGBA(tex, surface, slice) { + surface = surface || 0; slice = slice || 0; + const m = surface % tex.mipLevels, [w, h, d] = mipSize(tex, m), n = w * h; + const data = tex.surfaces[surface], rgba = new Uint8ClampedArray(n * 4); + let src, ch = tex.channels, kind = tex.payload === 1 ? 'u8' : ['u8', 'u16', 'f16', 'f32'][tex.sample]; + if (tex.payload === 1) { + const per = data.length / d, blk = decodeBlocks(tex.format, data.subarray(per * slice, per * (slice + 1)), w, h); + if (!blk) fail('unsupported', 'ETC2 / EAC / ASTC blocks cannot be shown'); + if (blk instanceof Uint16Array) { src = blk; ch = 3; kind = 'f16'; } + else { + rgba.set(blk); + if (tex.channels === 3) for (let i = 0; i < n; i++) rgba[i * 4 + 3] = 255; + return { width: w, height: h, rgba }; + } + } else { + src = data.subarray(n * ch * slice, n * ch * (slice + 1)); + } + for (let i = 0; i < n; i++) { + const v = [0, 0, 0, 255]; + for (let c = 0; c < ch; c++) { + const x = src[i * ch + c], alpha = (ch === 2 && c === 1) || (ch === 4 && c === 3); + let q; + if (kind === 'u8') q = x; + else if (kind === 'u16') q = Math.round(x / 257); + else { + const f = kind === 'f16' ? halfToFloat(x) : x; + q = alpha ? Math.max(0, Math.min(255, Math.round(f * 255))) : linToSrgb(f); + } + v[ch <= 2 ? (c === 0 ? 0 : 3) : c] = q; + } + if (ch <= 2) v[1] = v[2] = v[0]; + rgba.set(v, i * 4); + } + return { width: w, height: h, rgba }; + } + + function surfaceIndex(tex, frame, layer, face, mip) { + return (((frame || 0) * tex.layers + (layer || 0)) * tex.faces + (face || 0)) * tex.mipLevels + (mip || 0); + } /* ── Decoder ──────────────────────────────────────────────────────── */ @@ -102,7 +776,7 @@ R[r0 + tw] = R[r0 + tw - 1]; } - function decodeTile(b, p, end, out, imgW, tx, ty, tw, th, ch) { + function decodeTile(b, p, end, out, imgW, tx, ty, tw, th, ch, v2, book) { if (end <= p) corrupt(); let hdr = b[p++]; if (hdr === STORED) { @@ -112,10 +786,18 @@ out.set(b.subarray(p + rowb * y, p + rowb * (y + 1)), ((ty + y) * imgW + tx) * ch); return; } - if (hdr & 0xE0) corrupt(); /* reserved bits set */ - const xf = hdr & SUBGREEN; + if (hdr & (v2 ? 0x80 : 0xE0)) corrupt(); /* reserved bits set */ + if ((hdr & 0x40) && (hdr & SUBGREEN)) corrupt(); + /* xf: 0 YCoCg-R, 1 subtract-green, 2 none (v2) */ + const xf = (hdr & 0x40) ? 2 : (hdr & SUBGREEN) ? 1 : 0, ent = hdr & 0x20; hdr &= 0x0F; if ((hdr >> ch) || (xf && ch < 3)) corrupt(); + let emask = 0; + if (ent) { /* v2: entropy planes */ + if (p >= end || !book) corrupt(); + emask = b[p++]; + if ((emask >> ch) || (emask & hdr)) corrupt(); + } const rowLen = TILE + 1, planeLen = RING * rowLen; const rows = new Uint8Array(4 * planeLen); @@ -128,7 +810,8 @@ for (let c = 0; c < ch; c++) { if (hdr & (1 << c)) continue; z[c] = new Uint8Array(TILE_PX + GROUP); - const r = unpackPlane(b, p, end, z[c], tw, th); + const r = (emask & (1 << c)) ? huffPlane(b, p, end, z[c], tw, th, book, xf * 16 + c) + : unpackPlane(b, p, end, z[c], tw, th); p = r.p; filt[c] = r.filt; } if (p !== end) corrupt(); @@ -147,7 +830,11 @@ const P0 = ri, P1 = planeLen + ri, P2 = 2 * planeLen + ri, P3 = 3 * planeLen + ri; for (let x = 0; x < tw; x++, d += ch) { if (ch >= 3) { - if (xf) { + if (xf === 2) { + out[d] = rows[P0 + x]; + out[d + 1] = rows[P1 + x]; + out[d + 2] = rows[P2 + x]; + } else if (xf) { const g = rows[P0 + x]; out[d] = rows[P1 + x] + g - 128; out[d + 1] = g; @@ -171,11 +858,27 @@ const r32 = (b, p) => (b[p] | (b[p + 1] << 8) | (b[p + 2] << 16) | (b[p + 3] << 24)) >>> 0; + /* decode(bytes) → 8-bit image. Reads version 1, and version 2 files + * that hold 8-bit pixels (2D; cube face 0 / layer 0 / frame 0). Other + * textures: see decodeTexture. */ function decode(input) { const b = input instanceof Uint8Array ? input : new Uint8Array(input); + if (b.length >= 5 && b[0] === 0x59 && b[1] === 0x41 && b[2] === 0x50 && b[3] === 0x46 && b[4] === 2) { + const t = decodeTexture(b); + if (t.payload !== 0 || t.sample !== 0 || t.shape === 2) + throw new YapfError('unsupported', 'this is a YAPF 2 texture (GPU blocks, 16-bit / float or a ' + + 'volume); use YAPF.decodeTexture and YAPF.textureToRGBA'); + const mips = t.surfaces.slice(0, t.mipLevels); + return { width: t.width, height: t.height, channels: t.channels, gpuFormat: t.format, + flags: t.flags, mipLevels: t.mipLevels, pixels: mips[0], mips }; + } + return decodeV1(b); + } + + function decodeV1(b) { if (b.length < 20 || b[0] !== 0x59 || b[1] !== 0x41 || b[2] !== 0x50 || b[3] !== 0x46) - throw new Error('YAPF: not a YAPF file'); - if (b[4] !== 1) throw new Error('YAPF: unsupported format version ' + b[4]); + throw new YapfError('not-yapf', 'not a YAPF file'); + if (b[4] !== 1) throw new YapfError('version', 'unsupported format version ' + b[4]); const ch = b[5], mipLevels = b[7], W = r32(b, 12), H = r32(b, 16); if (ch < 1 || ch > 4 || mipLevels < 1 || mipLevels > MAX_MIPS || W < 1 || W > MAX_DIM || H < 1 || H > MAX_DIM) corrupt(); @@ -459,5 +1162,7 @@ return out; } - return { decode, encode, toRGBA, GPU, FLAG, VERSION: 1 }; + return { decode, encode, toRGBA, decodeTexture, textureToRGBA, decodeBlocks, surfaceIndex, + checksum: (b) => checksum(b, 0, b.length), YapfError, GPU, FLAG, NEED, + VERSION: 1 /* written by encode */, READS: [1, 2] }; })); diff --git a/extensions/figma/README.md b/extensions/figma/README.md index 487853f..ac80b84 100644 --- a/extensions/figma/README.md +++ b/extensions/figma/README.md @@ -9,6 +9,12 @@ Both directions are pixel-exact: the plugin carries its own small PNG encoder/decoder instead of going through a canvas, which would alter semi-transparent pixels. +Import reads every YAPF file, version 1 and 2. 8-bit images keep their +exact pixels; GPU blocks (BC1–BC7, BC6H), 16-bit and float textures are +converted to 8-bit for display (Figma images are 8-bit); cubes, arrays, +volumes and animations bring their first image. Damaged files are +reported as such. + ## Install (development plugin) 1. Use the **Figma desktop app**. diff --git a/extensions/figma/src/ui.src.html b/extensions/figma/src/ui.src.html index ca131a0..e804d73 100644 --- a/extensions/figma/src/ui.src.html +++ b/extensions/figma/src/ui.src.html @@ -64,14 +64,26 @@ $('tab-import').onclick = () => tab('import'); $('tab-export').onclick = () => tab('export'); /* Import: .yapf → pixels → exact PNG → Figma image */ +/* Any YAPF file → 8-bit pixels. 8-bit images keep their exact pixels and + * channels; GPU blocks, 16-bit and float textures are converted for display + * (RGBA). Cubes, arrays, volumes and animations give their first image. */ +function yapfToPixels(tex) { + const w = tex.width, h = tex.height; + if (tex.payload === 0 && tex.sample === 0) + return { width: w, height: h, channels: tex.channels, + pixels: tex.surfaces[0].subarray(0, w * h * tex.channels) }; + const v = YAPF.textureToRGBA(tex, 0, 0); + return { width: w, height: h, channels: 4, pixels: new Uint8Array(v.rgba.buffer), + note: tex.payload === 1 ? 'GPU blocks decoded' : 'converted to 8-bit' }; +} async function importFiles(files) { const images = []; for (const f of files) { try { - const img = YAPF.decode(new Uint8Array(await f.arrayBuffer())); + const img = yapfToPixels(YAPF.decodeTexture(new Uint8Array(await f.arrayBuffer()))); const png = await PNG.encode(img.pixels, img.width, img.height, img.channels); images.push({ name: f.name.replace(/\.yapf$/i, ''), width: img.width, height: img.height, png }); - status('Decoded ' + f.name + ' (' + img.width + '×' + img.height + ')'); + status('Decoded ' + f.name + ' (' + img.width + '×' + img.height + (img.note ? ', ' + img.note : '') + ')'); } catch (e) { status(f.name + ': ' + e.message); } diff --git a/extensions/figma/ui.html b/extensions/figma/ui.html index 0b419ac..167bd23 100644 --- a/extensions/figma/ui.html +++ b/extensions/figma/ui.html @@ -47,7 +47,7 @@