forked from external-repos/squoosh
Compare commits
621 Commits
karmatic
...
webpack-wa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0ee830a9a | ||
|
|
73d1aa0332 | ||
|
|
b8f801333d | ||
|
|
499956e216 | ||
|
|
3932ee2c00 | ||
|
|
68177b7590 | ||
|
|
0a2a4122dc | ||
|
|
e6299569d0 | ||
|
|
578ad8c291 | ||
|
|
eaa294b689 | ||
|
|
6e97cfb8d5 | ||
|
|
80a68c48b2 | ||
|
|
98865f8141 | ||
|
|
78da9fd144 | ||
|
|
94c50a989b | ||
|
|
3e525e767c | ||
|
|
dfcc1e24e4 | ||
|
|
f3aa8edfca | ||
|
|
ffd7ee6013 | ||
|
|
64bb09dbc5 | ||
|
|
35fcbc40ed | ||
|
|
1b55a48680 | ||
|
|
88fbb19d29 | ||
|
|
3dc1501ff7 | ||
|
|
b7576c559a | ||
|
|
69ed2e1d56 | ||
|
|
55f7f3d72a | ||
|
|
055d0b4ea1 | ||
|
|
7735346ed0 | ||
|
|
2a06fdbbe0 | ||
|
|
19169199e9 | ||
|
|
76f3c39b78 | ||
|
|
8da52acc4c | ||
|
|
9253522a3a | ||
|
|
cf39a3e5a5 | ||
|
|
a08877f0ac | ||
|
|
2f0dc1c067 | ||
|
|
535d8c9142 | ||
|
|
6dc2ba3bef | ||
|
|
93cfdc8f98 | ||
|
|
671544349e | ||
|
|
d3c1939692 | ||
|
|
99642aef96 | ||
|
|
ac4942b640 | ||
|
|
23cb004a86 | ||
|
|
4b6de60978 | ||
|
|
de204aa56a | ||
|
|
976f811b36 | ||
|
|
855fc9e602 | ||
|
|
a8848e717c | ||
|
|
90b99faf8b | ||
|
|
d3cfffbbcf | ||
|
|
fade7f9be8 | ||
|
|
b10dd055d3 | ||
|
|
7532f01222 | ||
|
|
2df09efdee | ||
|
|
bed4f49a12 | ||
|
|
2b7fefff8b | ||
|
|
bdcf2519ce | ||
|
|
3f8afb72a6 | ||
|
|
2f834db224 | ||
|
|
3541ce7492 | ||
|
|
b6fae0eb4c | ||
|
|
fbaa282f07 | ||
|
|
6136ae7411 | ||
|
|
f024747299 | ||
|
|
66feffcc49 | ||
|
|
24254df7db | ||
|
|
cae73f1f1b | ||
|
|
073a52213e | ||
|
|
0d34485a00 | ||
|
|
65519d539e | ||
|
|
ec44a8e817 | ||
|
|
920f225cb0 | ||
|
|
d5d4bd61ff | ||
|
|
5656d10b67 | ||
|
|
adb4b6468b | ||
|
|
0a293d7f63 | ||
|
|
27cb5afd5b | ||
|
|
5e58fc6b04 | ||
|
|
e820adfc96 | ||
|
|
5a8a114dcb | ||
|
|
a1c685e820 | ||
|
|
377dcfcc1b | ||
|
|
913e67ca93 | ||
|
|
fb1a97c7d4 | ||
|
|
42eef6945d | ||
|
|
d04b08d640 | ||
|
|
c547dd10d3 | ||
|
|
f4579da9c9 | ||
|
|
37dc585d80 | ||
|
|
bc0a425a0f | ||
|
|
b696f246a1 | ||
|
|
e6e197e140 | ||
|
|
1c0e8a1fd3 | ||
|
|
e3e154fa1a | ||
|
|
73b7c437f9 | ||
|
|
719168be77 | ||
|
|
a2021b175c | ||
|
|
9a388fbd13 | ||
|
|
1ba0452540 | ||
|
|
73df9f18f0 | ||
|
|
0e7521877b | ||
|
|
120b37c192 | ||
|
|
5f3502b838 | ||
|
|
33f99432c5 | ||
|
|
85756ff5df | ||
|
|
84e567ad6a | ||
|
|
39281331fa | ||
|
|
438ce2ce63 | ||
|
|
a13e17e256 | ||
|
|
cd6db2d776 | ||
|
|
a08662b617 | ||
|
|
003ec9de35 | ||
|
|
e7f76ca0b8 | ||
|
|
21111e2927 | ||
|
|
445c3ef32c | ||
|
|
9df5542ee1 | ||
|
|
fffc4a0cd1 | ||
|
|
50a8743be3 | ||
|
|
8480bc7dbd | ||
|
|
72e4546922 | ||
|
|
11be5babca | ||
|
|
17e5db2427 | ||
|
|
465093eb07 | ||
|
|
b430ac1041 | ||
|
|
ea96847c1e | ||
|
|
3b5106a61d | ||
|
|
9f611b0b52 | ||
|
|
18a6b3c3e5 | ||
|
|
d9ed4e18ea | ||
|
|
9e757aa896 | ||
|
|
89b58bb446 | ||
|
|
e80ca583cc | ||
|
|
2ecc81b34f | ||
|
|
60e98ee34f | ||
|
|
538ea89ea9 | ||
|
|
3990e11e0a | ||
|
|
0251f88fe5 | ||
|
|
bbcb959b11 | ||
|
|
b5e928bac9 | ||
|
|
6592dee4a9 | ||
|
|
9b3d72191e | ||
|
|
a92e5b48ff | ||
|
|
e355764ab0 | ||
|
|
c5efd5a8bf | ||
|
|
385461944b | ||
|
|
8385ba3274 | ||
|
|
6ca6a77595 | ||
|
|
14c837e894 | ||
|
|
33346d7cb6 | ||
|
|
05d4f531e2 | ||
|
|
ede2c49b12 | ||
|
|
16acd32c68 | ||
|
|
cc90192860 | ||
|
|
3607005fa8 | ||
|
|
7646a64f94 | ||
|
|
c8ce6ce27b | ||
|
|
1240474c4b | ||
|
|
f5e84441c0 | ||
|
|
6bc19d78bc | ||
|
|
a4437d2873 | ||
|
|
cd19650748 | ||
|
|
253315b3b1 | ||
|
|
4203ad9a13 | ||
|
|
6958202f9d | ||
|
|
386fef063f | ||
|
|
d7246ca427 | ||
|
|
fd024853b6 | ||
|
|
bd53d17876 | ||
|
|
3f87f571f4 | ||
|
|
1c69a6f1b7 | ||
|
|
82419cbb6e | ||
|
|
db65630c8d | ||
|
|
b8e54b947f | ||
|
|
f23897108d | ||
|
|
1efe5b21f0 | ||
|
|
fc71b4d249 | ||
|
|
dc81d46556 | ||
|
|
56c2080f43 | ||
|
|
4cc50fcaa5 | ||
|
|
2d67562576 | ||
|
|
76f2d7afa7 | ||
|
|
80fa9c4f21 | ||
|
|
8f215a5b4b | ||
|
|
bffd9cb52a | ||
|
|
74b1ff5b10 | ||
|
|
3c0079fea0 | ||
|
|
c0a9723d20 | ||
|
|
a4f0a76200 | ||
|
|
eb57b0130b | ||
|
|
f8c37c7abc | ||
|
|
72373f8812 | ||
|
|
b285e99e7d | ||
|
|
f9a6b88bb6 | ||
|
|
4a65d506f2 | ||
|
|
3bc03c90fd | ||
|
|
c35dfa4ac5 | ||
|
|
1d2a9a9dde | ||
|
|
925220bb13 | ||
|
|
16d088bfe3 | ||
|
|
b8e22ee435 | ||
|
|
60dea4b932 | ||
|
|
8ae1a42e4b | ||
|
|
cfdc7a46e6 | ||
|
|
afb23adcbf | ||
|
|
3b84a474b8 | ||
|
|
e96bb04e88 | ||
|
|
2e3b8507b2 | ||
|
|
e12c69f1a6 | ||
|
|
d049a23469 | ||
|
|
2633f427c8 | ||
|
|
ff920f1d7b | ||
|
|
fd69560025 | ||
|
|
ba51e47e05 | ||
|
|
409f552274 | ||
|
|
69a7c184bd | ||
|
|
3039c84738 | ||
|
|
bfa5cd085d | ||
|
|
7c282b30b1 | ||
|
|
06fa3c541e | ||
|
|
c9e31ac1f7 | ||
|
|
e248486d3d | ||
|
|
b32a52d236 | ||
|
|
a24b4d6d4d | ||
|
|
b831aa0075 | ||
|
|
bf4d4b78cb | ||
|
|
496896e36e | ||
|
|
6b88ec1f8a | ||
|
|
3af5f3a96d | ||
|
|
ddc5564515 | ||
|
|
bc5da7ef06 | ||
|
|
45221c0b03 | ||
|
|
d29cf2ffa7 | ||
|
|
f6c0b89d1f | ||
|
|
ecd9e06665 | ||
|
|
9e5b66d5f4 | ||
|
|
8c35c3cdaa | ||
|
|
828a6240fe | ||
|
|
eaad0eaee0 | ||
|
|
db76d4417c | ||
|
|
7a6c6ec210 | ||
|
|
8e034f183b | ||
|
|
5a01b34cce | ||
|
|
1399a9bffe | ||
|
|
653c6ed85a | ||
|
|
ebbb7b58cb | ||
|
|
7164e4e315 | ||
|
|
23398d07f9 | ||
|
|
ec2bc3efa2 | ||
|
|
86d78763c1 | ||
|
|
fb5ae36d7e | ||
|
|
51f812625b | ||
|
|
479bfee647 | ||
|
|
a3501a56cd | ||
|
|
c353e286b0 | ||
|
|
8ed01e8a87 | ||
|
|
36ed21b9f4 | ||
|
|
cca41bb449 | ||
|
|
8f787ad0e6 | ||
|
|
9c1170f100 | ||
|
|
5432be4a3f | ||
|
|
7cae821db5 | ||
|
|
19ebb24f03 | ||
|
|
d07512566e | ||
|
|
61929666f3 | ||
|
|
792ffbfcd7 | ||
|
|
9685271bb4 | ||
|
|
5b1a6cc95e | ||
|
|
bf34075e6a | ||
|
|
f1859eeef2 | ||
|
|
fa12b37e53 | ||
|
|
520a5dc9f2 | ||
|
|
7af949b5a5 | ||
|
|
300612b09b | ||
|
|
6f00e9825c | ||
|
|
6ca9c5300e | ||
|
|
cdeb31051b | ||
|
|
ba90517ad7 | ||
|
|
7aff949f47 | ||
|
|
0e8c0da3dd | ||
|
|
3132a207e1 | ||
|
|
88dd0e06c5 | ||
|
|
f507a2464f | ||
|
|
14baa6ebf8 | ||
|
|
5d32126565 | ||
|
|
484ff7ab4c | ||
|
|
36f86385a2 | ||
|
|
436faa17af | ||
|
|
d205ae206f | ||
|
|
6baa5900fc | ||
|
|
fadb53f075 | ||
|
|
1a63387408 | ||
|
|
a316120b69 | ||
|
|
0d1e5ef119 | ||
|
|
b49cfca39d | ||
|
|
ab58df4c2c | ||
|
|
db20f10bd2 | ||
|
|
444cc5a193 | ||
|
|
6c253bc9b4 | ||
|
|
2fd28e174e | ||
|
|
a188692c88 | ||
|
|
b263419e08 | ||
|
|
826e06c727 | ||
|
|
dfcdfb105f | ||
|
|
0508bbb16f | ||
|
|
dfbfa85fd3 | ||
|
|
b99ad4bdc3 | ||
|
|
e801170496 | ||
|
|
91e7c9c5ad | ||
|
|
ca5162ed32 | ||
|
|
0bf87d0c87 | ||
|
|
ce91eb5bae | ||
|
|
8d68056bca | ||
|
|
d0de8e444a | ||
|
|
dfef1f21cc | ||
|
|
2440ac4e87 | ||
|
|
e90db78697 | ||
|
|
5ae15d429c | ||
|
|
89d6b46f3e | ||
|
|
e086f64779 | ||
|
|
9ed3b4f11e | ||
|
|
ece3fa12b4 | ||
|
|
9a35224535 | ||
|
|
ef3faa58bc | ||
|
|
b6a8f7eeba | ||
|
|
d1203d9c42 | ||
|
|
a834b6ae38 | ||
|
|
e7982a73ad | ||
|
|
717342c80c | ||
|
|
075f0e62fd | ||
|
|
bcca31fbed | ||
|
|
007891fc11 | ||
|
|
f8e41952d1 | ||
|
|
e4d64f8a79 | ||
|
|
1654f69ec1 | ||
|
|
cb16fb5437 | ||
|
|
36f5fa2c47 | ||
|
|
51ad22e72c | ||
|
|
1a355c0c16 | ||
|
|
fe5ba08963 | ||
|
|
7fc994d4af | ||
|
|
a0a8285e02 | ||
|
|
da2e35f613 | ||
|
|
09bdc25352 | ||
|
|
ad263a9c36 | ||
|
|
c8d8d4e43d | ||
|
|
94249b8a93 | ||
|
|
edd2c51eb6 | ||
|
|
1d24e9399f | ||
|
|
3a0062276d | ||
|
|
1993cf3f6c | ||
|
|
c97aac31c6 | ||
|
|
507921cbe8 | ||
|
|
887db675c8 | ||
|
|
3917618e4e | ||
|
|
3c42d2e6a4 | ||
|
|
db8777b7f7 | ||
|
|
18c2cddee2 | ||
|
|
3ff9d3a1fa | ||
|
|
6503667c78 | ||
|
|
0fa95f84d4 | ||
|
|
cf91a90270 | ||
|
|
690052f989 | ||
|
|
b3e935f7e4 | ||
|
|
17314ebd29 | ||
|
|
adc437cd51 | ||
|
|
0e97b74510 | ||
|
|
9ffb475cac | ||
|
|
faa2b030c5 | ||
|
|
e3b3b10e2a | ||
|
|
b569cf268c | ||
|
|
b154b77556 | ||
|
|
84c0f30a7c | ||
|
|
16463ff76d | ||
|
|
8314e9e24b | ||
|
|
a33c557818 | ||
|
|
6fbdc65ad0 | ||
|
|
9c9b6c4711 | ||
|
|
46278d04c3 | ||
|
|
c1c16508b5 | ||
|
|
ed1b983711 | ||
|
|
ec23e28eda | ||
|
|
d48b49e8e4 | ||
|
|
14308970c6 | ||
|
|
38e86e1012 | ||
|
|
e9a33af831 | ||
|
|
6a63e5dbb2 | ||
|
|
1e1892a3d5 | ||
|
|
8bff9a2973 | ||
|
|
cbe753dd29 | ||
|
|
b047845b43 | ||
|
|
1bebc75381 | ||
|
|
93c46bfc8d | ||
|
|
a3d0f5963e | ||
|
|
006b82bf05 | ||
|
|
c36e37ac6b | ||
|
|
3cf6d7385a | ||
|
|
9045b2fa97 | ||
|
|
be6f3b9c6d | ||
|
|
5a699b7ce9 | ||
|
|
f366a78e87 | ||
|
|
c63c7ead51 | ||
|
|
ecfa5902cd | ||
|
|
444027b496 | ||
|
|
9c5dcb93c7 | ||
|
|
9594221271 | ||
|
|
01823d3b75 | ||
|
|
db07a90139 | ||
|
|
962d0928d3 | ||
|
|
e67d50c8e6 | ||
|
|
f9b2f17852 | ||
|
|
9746a9f5ed | ||
|
|
be0877ecb0 | ||
|
|
d2fcdfae43 | ||
|
|
2c9eb46941 | ||
|
|
d30a85fd48 | ||
|
|
9260bed1b1 | ||
|
|
f6d12985a9 | ||
|
|
10c9b1db7c | ||
|
|
4fb17be8de | ||
|
|
b592b1a088 | ||
|
|
0544a6507e | ||
|
|
1e20ff15ed | ||
|
|
04a0ec0645 | ||
|
|
f355292fe3 | ||
|
|
32e4d813de | ||
|
|
f960f5ea87 | ||
|
|
aa6f83e2fa | ||
|
|
c09e1f1895 | ||
|
|
7c311928dd | ||
|
|
5f1c8bcb6b | ||
|
|
93bc20f014 | ||
|
|
d29d9571c6 | ||
|
|
3d47dfc820 | ||
|
|
d7846c9add | ||
|
|
4d6fe9d641 | ||
|
|
205feba75d | ||
|
|
ca7663b94a | ||
|
|
83e45f054b | ||
|
|
783e893a67 | ||
|
|
0a941866a9 | ||
|
|
04edfe0085 | ||
|
|
6cae634eca | ||
|
|
8c7bf278dc | ||
|
|
f6106650b5 | ||
|
|
166e606034 | ||
|
|
c997e6a3e4 | ||
|
|
2a1b6dc9da | ||
|
|
129c33fa12 | ||
|
|
3245987113 | ||
|
|
593ad62cbb | ||
|
|
a625a76e9e | ||
|
|
c2a305304b | ||
|
|
7389c507fb | ||
|
|
68f0f23016 | ||
|
|
dc809dde30 | ||
|
|
80dfa03b94 | ||
|
|
fca7a5350d | ||
|
|
1b693fb57a | ||
|
|
7723bd3b5f | ||
|
|
723fc142ec | ||
|
|
06d4d946d9 | ||
|
|
428b7d976d | ||
|
|
32f2b4e573 | ||
|
|
b3ab983f02 | ||
|
|
e011724af4 | ||
|
|
f11a6cb38a | ||
|
|
adf6d3c60d | ||
|
|
bb8f35ce09 | ||
|
|
ae9ae31ddc | ||
|
|
67893817b5 | ||
|
|
f8da5b153d | ||
|
|
e2a956a088 | ||
|
|
5c5b001fc7 | ||
|
|
e4beafed97 | ||
|
|
553a504140 | ||
|
|
44dd2ee808 | ||
|
|
b36c851b2a | ||
|
|
0502d70cdf | ||
|
|
86546574bb | ||
|
|
f351712130 | ||
|
|
c7f2ae2234 | ||
|
|
436f689115 | ||
|
|
951c7af724 | ||
|
|
53b46f879f | ||
|
|
cbe82112ab | ||
|
|
7f5562ccfe | ||
|
|
76ec946616 | ||
|
|
68bb2edb39 | ||
|
|
9c85618aff | ||
|
|
aebeff8b4c | ||
|
|
8d63125b13 | ||
|
|
2ca97ef586 | ||
|
|
a1a00f0bfb | ||
|
|
6870b135b7 | ||
|
|
a0f1379feb | ||
|
|
9b17322478 | ||
|
|
f562bad286 | ||
|
|
6994cc3d15 | ||
|
|
9b572f9541 | ||
|
|
71f893cb44 | ||
|
|
6b76ea0a6f | ||
|
|
7616d33883 | ||
|
|
3c757bb2b2 | ||
|
|
a502df80ba | ||
|
|
921268ec58 | ||
|
|
7d42d4f973 | ||
|
|
e4e130c5d6 | ||
|
|
bcf7a63118 | ||
|
|
66aac12db7 | ||
|
|
59cd1f8930 | ||
|
|
150e704d20 | ||
|
|
b2d47f0fb8 | ||
|
|
bd3d33296d | ||
|
|
f4c82ced97 | ||
|
|
76188df0d3 | ||
|
|
9a58e4d339 | ||
|
|
f396a5b784 | ||
|
|
e572b853e2 | ||
|
|
726c2f195a | ||
|
|
4599e51b1e | ||
|
|
d93169cc5a | ||
|
|
bdd3c11f1a | ||
|
|
0cec90c7ca | ||
|
|
43def798e1 | ||
|
|
02b0c022ca | ||
|
|
c82d0d1b88 | ||
|
|
e24d7865ce | ||
|
|
a79f95b305 | ||
|
|
49b40b1c3e | ||
|
|
11ee74e224 | ||
|
|
f335246673 | ||
|
|
ccb734aec6 | ||
|
|
568b9e9459 | ||
|
|
a43ea761f5 | ||
|
|
577c77cc30 | ||
|
|
d2f60baef9 | ||
|
|
64acc08cd7 | ||
|
|
a1f0b81dff | ||
|
|
48bb58dc89 | ||
|
|
765cc213d2 | ||
|
|
37f5c0dd76 | ||
|
|
b25d1eaf86 | ||
|
|
248676aa31 | ||
|
|
059c80c05d | ||
|
|
cfd42818b7 | ||
|
|
5e66e0acc4 | ||
|
|
c9fe5ffbcf | ||
|
|
1b630a092f | ||
|
|
09e60284cb | ||
|
|
76b34c62db | ||
|
|
9d7212bc1d | ||
|
|
1b69c9231d | ||
|
|
bcd88f6356 | ||
|
|
2a47f67214 | ||
|
|
5e8dc1b26c | ||
|
|
c591f1f37d | ||
|
|
4db43ccd4e | ||
|
|
ea5d3c2d78 | ||
|
|
700b1f15cd | ||
|
|
485ba174e3 | ||
|
|
32f6f8b941 | ||
|
|
54ad30a7ed | ||
|
|
170d75482e | ||
|
|
a8db2b30f2 | ||
|
|
e3b1b08424 | ||
|
|
8006a1a5e7 | ||
|
|
1ae65dd4a1 | ||
|
|
bff515b63f | ||
|
|
65c3ea826f | ||
|
|
602d5140f9 | ||
|
|
44f0700332 | ||
|
|
c90db020b0 | ||
|
|
ef4094885e | ||
|
|
b52d9d9194 | ||
|
|
d3f2836f48 | ||
|
|
27722f77f9 | ||
|
|
3a0db14c40 | ||
|
|
e0dc1b48ec | ||
|
|
009327c2c4 | ||
|
|
b16d60b52b | ||
|
|
c550fe9283 | ||
|
|
dce4fc70ac | ||
|
|
b3f3ecbf28 | ||
|
|
e8c0ddfc7f | ||
|
|
a002b376af | ||
|
|
2165383da4 | ||
|
|
5fbf6b297f | ||
|
|
9d5ad83ff8 | ||
|
|
07f17dece2 | ||
|
|
f2f467ecb8 | ||
|
|
2ea9e22b52 | ||
|
|
4ee5572d2f | ||
|
|
df7e112d22 | ||
|
|
13ac3ed5b2 | ||
|
|
b7c223bc0d | ||
|
|
0f08121596 | ||
|
|
b15545402a | ||
|
|
b310c97044 | ||
|
|
307c6b05ae | ||
|
|
77a6d21924 | ||
|
|
d22a343378 | ||
|
|
790a5b580d | ||
|
|
6e8f8bbe41 | ||
|
|
cc9d01a9ab | ||
|
|
526520c399 | ||
|
|
acbc31bc35 | ||
|
|
e8e151a926 | ||
|
|
835a537c55 | ||
|
|
23ea9fad49 | ||
|
|
491280935a | ||
|
|
900eda9a8e | ||
|
|
38d0057833 | ||
|
|
3867448aad | ||
|
|
807a76d443 | ||
|
|
3e26a0a3cc | ||
|
|
68729979e3 | ||
|
|
a09ec269b8 | ||
|
|
3f18c927f1 | ||
|
|
9add650b75 |
13
.babelrc
13
.babelrc
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"plugins": [
|
|
||||||
"transform-class-properties",
|
|
||||||
"transform-react-constant-elements",
|
|
||||||
"transform-react-remove-prop-types",
|
|
||||||
[
|
|
||||||
"transform-react-jsx",
|
|
||||||
{
|
|
||||||
"pragma": "h"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"projects": {
|
|
||||||
"default": "squoosh-beta"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Something is not working as expected
|
||||||
|
labels:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Before you start**
|
||||||
|
Please take a look at the [FAQ](https://github.com/GoogleChromeLabs/squoosh/wiki/FAQ) as well as the already opened issues! If nothing fits your problem, go ahead and fill out the following template:
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Version:**
|
||||||
|
- OS w/ version: [e.g. iOS 12]
|
||||||
|
- Browser w/ version [e.g. Chrome 70]
|
||||||
|
- Node version: [e.g. 10.11.0]
|
||||||
|
- npm version: [e.g. 6.4.1]
|
||||||
|
|
||||||
|
**Is your issue related to the quality of image compression?**
|
||||||
|
Please attach original and output images (you can drag & drop to attach).
|
||||||
|
- Original image
|
||||||
|
- Output image from Squoosh
|
||||||
|
|
||||||
|
**Additional context, screenshots, screencasts**
|
||||||
|
Add any other context about the problem here.
|
||||||
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
labels:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Does other service/app have this feature?**
|
||||||
|
Add any service you know/use that has this feature (We want to know for research)
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ node_modules
|
|||||||
/*.log
|
/*.log
|
||||||
*.scss.d.ts
|
*.scss.d.ts
|
||||||
*.css.d.ts
|
*.css.d.ts
|
||||||
|
*.o
|
||||||
|
|||||||
4
.travis.yml
Normal file
4
.travis.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
language: node_js
|
||||||
|
cache: npm
|
||||||
|
script: npm run build
|
||||||
|
after_success: npm run sizereport
|
||||||
32
README.md
32
README.md
@@ -1,5 +1,31 @@
|
|||||||
# Squoosh!
|
# [Squoosh]!
|
||||||
|
|
||||||
Squoosh will be an image compression web app that allows you to dive into the
|
[Squoosh] is an image compression web app that allows you to dive into the advanced options provided
|
||||||
advanced options provided by various image compressors.
|
by various image compressors.
|
||||||
|
|
||||||
|
# Privacy
|
||||||
|
|
||||||
|
Google Analytics is used to record the following:
|
||||||
|
|
||||||
|
* [Basic visit data](https://support.google.com/analytics/answer/6004245?ref_topic=2919631).
|
||||||
|
* Before and after image size once an image is downloaded. These values are rounded to the nearest
|
||||||
|
kilobyte.
|
||||||
|
|
||||||
|
Image compression is handled locally; no additional data is sent to the server.
|
||||||
|
|
||||||
|
# Building locally
|
||||||
|
|
||||||
|
Clone the repo, and:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
You can run the development server with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
[Squoosh]: https://squoosh.app
|
||||||
|
|||||||
18
_headers.ejs
Normal file
18
_headers.ejs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Long-term cache by default.
|
||||||
|
/*
|
||||||
|
Cache-Control: max-age=31536000
|
||||||
|
|
||||||
|
# And here are the exceptions:
|
||||||
|
/
|
||||||
|
Cache-Control: no-cache
|
||||||
|
|
||||||
|
/serviceworker.js
|
||||||
|
Cache-Control: no-cache
|
||||||
|
|
||||||
|
/manifest.json
|
||||||
|
Cache-Control: must-revalidate, max-age=3600
|
||||||
|
|
||||||
|
# URLs in /assets do not include a hash and are mutable.
|
||||||
|
# But it isn't a big deal if the user gets an old version.
|
||||||
|
/assets/*
|
||||||
|
Cache-Control: must-revalidate, max-age=3600
|
||||||
2
_redirects.ejs
Normal file
2
_redirects.ejs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/index.html / 301
|
||||||
|
/* /index.html 301
|
||||||
@@ -11,6 +11,6 @@ $ npm install
|
|||||||
$ npm run build
|
$ npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
This will build two files: `<codec name>_<enc or dec>.js` and `<codec name>_<enc or dec>.wasm`. It will most likely be necessary to set [`Module["locateFile"]`](https://kripken.github.io/emscripten-site/docs/api_reference/module.html#affecting-execution) to sucessfully load the `.wasm` file. When the `.js` file is loaded, a global `<codec name>_<enc or dec>` is created with the same API as an [Emscripten `Module`](https://kripken.github.io/emscripten-site/docs/api_reference/module.html).
|
This will build two files: `<codec name>_<enc or dec>.js` and `<codec name>_<enc or dec>.wasm`. It will most likely be necessary to set [`Module["locateFile"]`](https://kripken.github.io/emscripten-site/docs/api_reference/module.html#affecting-execution) to successfully load the `.wasm` file. When the `.js` file is loaded, a global `<codec name>_<enc or dec>` is created with the same API as an [Emscripten `Module`](https://kripken.github.io/emscripten-site/docs/api_reference/module.html).
|
||||||
|
|
||||||
Each codec will document its API in its README.
|
Each codec will document its API in its README.
|
||||||
|
|||||||
BIN
codecs/example_palette.png
Normal file
BIN
codecs/example_palette.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 239 KiB |
5
codecs/hqx/.gitignore
vendored
Normal file
5
codecs/hqx/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
**/*.rs.bk
|
||||||
|
target
|
||||||
|
Cargo.lock
|
||||||
|
bin/
|
||||||
|
pkg/README.md
|
||||||
37
codecs/hqx/Cargo.toml
Normal file
37
codecs/hqx/Cargo.toml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
[package]
|
||||||
|
name = "squooshhqx"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Surma <surma@surma.link>"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["console_error_panic_hook", "wee_alloc"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cfg-if = "0.1.2"
|
||||||
|
wasm-bindgen = "0.2.38"
|
||||||
|
# lazy_static = "1.0.0"
|
||||||
|
hqx = {git = "https://github.com/CryZe/wasmboy-rs", tag="v0.1.2"}
|
||||||
|
|
||||||
|
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||||
|
# logging them with `console.error`. This is great for development, but requires
|
||||||
|
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||||
|
# code size when deploying.
|
||||||
|
console_error_panic_hook = { version = "0.1.1", optional = true }
|
||||||
|
|
||||||
|
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
|
||||||
|
# compared to the default allocator's ~10K. It is slower than the default
|
||||||
|
# allocator, however.
|
||||||
|
#
|
||||||
|
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
|
||||||
|
wee_alloc = { version = "0.4.2", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
wasm-bindgen-test = "0.2"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
# Tell `rustc` to optimize for small code size.
|
||||||
|
opt-level = "s"
|
||||||
|
lto = true
|
||||||
12
codecs/hqx/Dockerfile
Normal file
12
codecs/hqx/Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
FROM rust
|
||||||
|
RUN rustup target add wasm32-unknown-unknown
|
||||||
|
RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||||
|
|
||||||
|
RUN mkdir /opt/binaryen && \
|
||||||
|
curl -L https://github.com/WebAssembly/binaryen/releases/download/1.38.32/binaryen-1.38.32-x86-linux.tar.gz | tar -xzf - -C /opt/binaryen --strip 1
|
||||||
|
|
||||||
|
RUN mkdir /opt/wabt && \
|
||||||
|
curl -L https://github.com/WebAssembly/wabt/releases/download/1.0.11/wabt-1.0.11-linux.tar.gz | tar -xzf - -C /opt/wabt --strip 1
|
||||||
|
|
||||||
|
ENV PATH="/opt/binaryen:/opt/wabt:${PATH}"
|
||||||
|
WORKDIR /src
|
||||||
25
codecs/hqx/build.sh
Executable file
25
codecs/hqx/build.sh
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling wasm"
|
||||||
|
echo "============================================="
|
||||||
|
(
|
||||||
|
wasm-pack build
|
||||||
|
wasm-strip pkg/squooshhqx_bg.wasm
|
||||||
|
echo "Optimising Wasm so it doesn't break Chrome (this takes like 10-15mins. get a cup of tea)"
|
||||||
|
echo "Once https://crbug.com/974804 is fixed, we can remove this step"
|
||||||
|
wasm-opt -Os --no-validation -o pkg/squooshhqx_bg.wasm pkg/squooshhqx_bg.wasm
|
||||||
|
rm pkg/.gitignore
|
||||||
|
)
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling wasm done"
|
||||||
|
echo "============================================="
|
||||||
|
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
|
echo "Did you update your docker image?"
|
||||||
|
echo "Run \`docker pull ubuntu\`"
|
||||||
|
echo "Run \`docker pull rust\`"
|
||||||
|
echo "Run \`docker build -t squoosh-hqx .\`"
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
4
codecs/hqx/package-lock.json
generated
Normal file
4
codecs/hqx/package-lock.json
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "hqx",
|
||||||
|
"lockfileVersion": 1
|
||||||
|
}
|
||||||
7
codecs/hqx/package.json
Normal file
7
codecs/hqx/package.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "hqx",
|
||||||
|
"scripts": {
|
||||||
|
"build:image": "docker build -t squoosh-hqx .",
|
||||||
|
"build": "docker run --rm -v $(pwd):/src squoosh-hqx ./build.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
codecs/hqx/pkg/package.json
Normal file
15
codecs/hqx/pkg/package.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "squooshhqx",
|
||||||
|
"collaborators": [
|
||||||
|
"Surma <surma@surma.link>"
|
||||||
|
],
|
||||||
|
"version": "0.1.0",
|
||||||
|
"files": [
|
||||||
|
"squooshhqx_bg.wasm",
|
||||||
|
"squooshhqx.js",
|
||||||
|
"squooshhqx.d.ts"
|
||||||
|
],
|
||||||
|
"module": "squooshhqx.js",
|
||||||
|
"types": "squooshhqx.d.ts",
|
||||||
|
"sideEffects": "false"
|
||||||
|
}
|
||||||
9
codecs/hqx/pkg/squooshhqx.d.ts
vendored
Normal file
9
codecs/hqx/pkg/squooshhqx.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/**
|
||||||
|
* @param {Uint32Array} input_image
|
||||||
|
* @param {number} input_width
|
||||||
|
* @param {number} input_height
|
||||||
|
* @param {number} factor
|
||||||
|
* @returns {Uint32Array}
|
||||||
|
*/
|
||||||
|
export function resize(input_image: Uint32Array, input_width: number, input_height: number, factor: number): Uint32Array;
|
||||||
46
codecs/hqx/pkg/squooshhqx.js
Normal file
46
codecs/hqx/pkg/squooshhqx.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import * as wasm from './squooshhqx_bg.wasm';
|
||||||
|
|
||||||
|
let cachegetUint32Memory = null;
|
||||||
|
function getUint32Memory() {
|
||||||
|
if (cachegetUint32Memory === null || cachegetUint32Memory.buffer !== wasm.memory.buffer) {
|
||||||
|
cachegetUint32Memory = new Uint32Array(wasm.memory.buffer);
|
||||||
|
}
|
||||||
|
return cachegetUint32Memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
let WASM_VECTOR_LEN = 0;
|
||||||
|
|
||||||
|
function passArray32ToWasm(arg) {
|
||||||
|
const ptr = wasm.__wbindgen_malloc(arg.length * 4);
|
||||||
|
getUint32Memory().set(arg, ptr / 4);
|
||||||
|
WASM_VECTOR_LEN = arg.length;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachegetInt32Memory = null;
|
||||||
|
function getInt32Memory() {
|
||||||
|
if (cachegetInt32Memory === null || cachegetInt32Memory.buffer !== wasm.memory.buffer) {
|
||||||
|
cachegetInt32Memory = new Int32Array(wasm.memory.buffer);
|
||||||
|
}
|
||||||
|
return cachegetInt32Memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArrayU32FromWasm(ptr, len) {
|
||||||
|
return getUint32Memory().subarray(ptr / 4, ptr / 4 + len);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {Uint32Array} input_image
|
||||||
|
* @param {number} input_width
|
||||||
|
* @param {number} input_height
|
||||||
|
* @param {number} factor
|
||||||
|
* @returns {Uint32Array}
|
||||||
|
*/
|
||||||
|
export function resize(input_image, input_width, input_height, factor) {
|
||||||
|
const retptr = 8;
|
||||||
|
const ret = wasm.resize(retptr, passArray32ToWasm(input_image), WASM_VECTOR_LEN, input_width, input_height, factor);
|
||||||
|
const memi32 = getInt32Memory();
|
||||||
|
const v0 = getArrayU32FromWasm(memi32[retptr / 4 + 0], memi32[retptr / 4 + 1]).slice();
|
||||||
|
wasm.__wbindgen_free(memi32[retptr / 4 + 0], memi32[retptr / 4 + 1] * 4);
|
||||||
|
return v0;
|
||||||
|
}
|
||||||
|
|
||||||
5
codecs/hqx/pkg/squooshhqx_bg.d.ts
vendored
Normal file
5
codecs/hqx/pkg/squooshhqx_bg.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
export const memory: WebAssembly.Memory;
|
||||||
|
export function __wbindgen_malloc(a: number): number;
|
||||||
|
export function __wbindgen_free(a: number, b: number): void;
|
||||||
|
export function resize(a: number, b: number, c: number, d: number, e: number, f: number): void;
|
||||||
BIN
codecs/hqx/pkg/squooshhqx_bg.wasm
Normal file
BIN
codecs/hqx/pkg/squooshhqx_bg.wasm
Normal file
Binary file not shown.
55
codecs/hqx/src/lib.rs
Normal file
55
codecs/hqx/src/lib.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
extern crate cfg_if;
|
||||||
|
extern crate hqx;
|
||||||
|
extern crate wasm_bindgen;
|
||||||
|
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
cfg_if! {
|
||||||
|
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||||
|
// allocator.
|
||||||
|
if #[cfg(feature = "wee_alloc")] {
|
||||||
|
extern crate wee_alloc;
|
||||||
|
#[global_allocator]
|
||||||
|
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn resize(
|
||||||
|
input_image: Vec<u32>,
|
||||||
|
input_width: usize,
|
||||||
|
input_height: usize,
|
||||||
|
factor: usize,
|
||||||
|
) -> Vec<u32> {
|
||||||
|
let num_output_pixels = input_width * input_height * factor * factor;
|
||||||
|
let mut output_image = Vec::<u32>::with_capacity(num_output_pixels * 4);
|
||||||
|
output_image.resize(num_output_pixels, 0);
|
||||||
|
|
||||||
|
match factor {
|
||||||
|
2 => hqx::hq2x(
|
||||||
|
input_image.as_slice(),
|
||||||
|
output_image.as_mut_slice(),
|
||||||
|
input_width,
|
||||||
|
input_height,
|
||||||
|
),
|
||||||
|
3 => hqx::hq3x(
|
||||||
|
input_image.as_slice(),
|
||||||
|
output_image.as_mut_slice(),
|
||||||
|
input_width,
|
||||||
|
input_height,
|
||||||
|
),
|
||||||
|
4 => hqx::hq4x(
|
||||||
|
input_image.as_slice(),
|
||||||
|
output_image.as_mut_slice(),
|
||||||
|
input_width,
|
||||||
|
input_height,
|
||||||
|
),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return output_image;
|
||||||
|
}
|
||||||
17
codecs/hqx/src/utils.rs
Normal file
17
codecs/hqx/src/utils.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use cfg_if::cfg_if;
|
||||||
|
|
||||||
|
cfg_if! {
|
||||||
|
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||||
|
// `set_panic_hook` function at least once during initialization, and then
|
||||||
|
// we will get better error messages if our code ever panics.
|
||||||
|
//
|
||||||
|
// For more details see
|
||||||
|
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||||
|
if #[cfg(feature = "console_error_panic_hook")] {
|
||||||
|
extern crate console_error_panic_hook;
|
||||||
|
pub use self::console_error_panic_hook::set_once as set_panic_hook;
|
||||||
|
} else {
|
||||||
|
#[inline]
|
||||||
|
pub fn set_panic_hook() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
codecs/imagequant/README.md
Normal file
30
codecs/imagequant/README.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# ImageQuant
|
||||||
|
|
||||||
|
- Source: <https://github.com/ImageOptim/libimagequant>
|
||||||
|
- Version: v2.12.1
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Docker
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
See `example.html`
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### `int version()`
|
||||||
|
|
||||||
|
Returns the version of libimagequant as a number. va.b.c is encoded as 0x0a0b0c
|
||||||
|
|
||||||
|
### `RawImage quantize(std::string buffer, int image_width, int image_height, int numColors, float dithering)`
|
||||||
|
|
||||||
|
Quantizes the given images, using at most `numColors`, a value between 2 and 256. `dithering` is a value between 0 and 1 controlling the amount of dithering. `RawImage` is a class with 3 fields: `buffer`, `width`, and `height`.
|
||||||
|
|
||||||
|
### `RawImage zx_quantize(std::string buffer, int image_width, int image_height, float dithering)`
|
||||||
|
|
||||||
|
???
|
||||||
|
|
||||||
|
### `void free_result()`
|
||||||
|
|
||||||
|
Frees the result created by `quantize()`.
|
||||||
48
codecs/imagequant/build.sh
Executable file
48
codecs/imagequant/build.sh
Executable file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
export OPTIMIZE="-Os"
|
||||||
|
export LDFLAGS="${OPTIMIZE}"
|
||||||
|
export CFLAGS="${OPTIMIZE}"
|
||||||
|
export CPPFLAGS="${OPTIMIZE}"
|
||||||
|
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling libimagequant"
|
||||||
|
echo "============================================="
|
||||||
|
(
|
||||||
|
emcc \
|
||||||
|
--bind \
|
||||||
|
${OPTIMIZE} \
|
||||||
|
-s ALLOW_MEMORY_GROWTH=1 \
|
||||||
|
-s MODULARIZE=1 \
|
||||||
|
-s 'EXPORT_NAME="imagequant"' \
|
||||||
|
-I node_modules/libimagequant \
|
||||||
|
--std=c99 \
|
||||||
|
-c \
|
||||||
|
node_modules/libimagequant/{libimagequant,pam,mediancut,blur,mempool,kmeans,nearest}.c
|
||||||
|
)
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling wasm module"
|
||||||
|
echo "============================================="
|
||||||
|
(
|
||||||
|
emcc \
|
||||||
|
--bind \
|
||||||
|
${OPTIMIZE} \
|
||||||
|
-s ALLOW_MEMORY_GROWTH=1 \
|
||||||
|
-s MODULARIZE=1 \
|
||||||
|
-s 'EXPORT_NAME="imagequant"' \
|
||||||
|
-I node_modules/libimagequant \
|
||||||
|
-o ./imagequant.js \
|
||||||
|
--std=c++11 *.o \
|
||||||
|
-x c++ \
|
||||||
|
imagequant.cpp
|
||||||
|
)
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling wasm module done"
|
||||||
|
echo "============================================="
|
||||||
|
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
|
echo "Did you update your docker image?"
|
||||||
|
echo "Run \`docker pull trzeci/emscripten\`"
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
41
codecs/imagequant/example.html
Normal file
41
codecs/imagequant/example.html
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<style>
|
||||||
|
canvas {
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src='imagequant.js'></script>
|
||||||
|
<script>
|
||||||
|
const Module = imagequant();
|
||||||
|
|
||||||
|
async function loadImage(src) {
|
||||||
|
// Load image
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = src;
|
||||||
|
await new Promise(resolve => img.onload = resolve);
|
||||||
|
// Make canvas same size as image
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
[canvas.width, canvas.height] = [img.width, img.height];
|
||||||
|
// Draw image onto canvas
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.drawImage(img, 0, 0);
|
||||||
|
return ctx.getImageData(0, 0, img.width, img.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
Module.onRuntimeInitialized = async _ => {
|
||||||
|
console.log('Version:', Module.version().toString(16));
|
||||||
|
const image = await loadImage('../example.png');
|
||||||
|
// const rawImage = Module.quantize(image.data, image.width, image.height, 256, 1.0);
|
||||||
|
const rawImage = Module.zx_quantize(image.data, image.width, image.height, 1.0);
|
||||||
|
console.log('done');
|
||||||
|
Module.free_result();
|
||||||
|
|
||||||
|
const imageData = new ImageData(new Uint8ClampedArray(rawImage.buffer), rawImage.width, rawImage.height);
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.width = image.width;
|
||||||
|
canvas.height = image.height;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
document.body.appendChild(canvas);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
245
codecs/imagequant/imagequant.cpp
Normal file
245
codecs/imagequant/imagequant.cpp
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
#include "emscripten/bind.h"
|
||||||
|
#include "emscripten/val.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "libimagequant.h"
|
||||||
|
|
||||||
|
using namespace emscripten;
|
||||||
|
|
||||||
|
int version() {
|
||||||
|
return (((LIQ_VERSION/10000) % 100) << 16) |
|
||||||
|
(((LIQ_VERSION/100 ) % 100) << 8) |
|
||||||
|
(((LIQ_VERSION/1 ) % 100) << 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
class RawImage {
|
||||||
|
public:
|
||||||
|
val buffer;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
|
||||||
|
RawImage(val b, int w, int h)
|
||||||
|
: buffer(b), width(w), height(h) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
liq_attr *attr;
|
||||||
|
liq_image *image;
|
||||||
|
liq_result *res;
|
||||||
|
uint8_t* result;
|
||||||
|
RawImage quantize(std::string rawimage, int image_width, int image_height, int num_colors, float dithering) {
|
||||||
|
const uint8_t* image_buffer = (uint8_t*)rawimage.c_str();
|
||||||
|
int size = image_width * image_height;
|
||||||
|
attr = liq_attr_create();
|
||||||
|
image = liq_image_create_rgba(attr, image_buffer, image_width, image_height, 0);
|
||||||
|
liq_set_max_colors(attr, num_colors);
|
||||||
|
liq_image_quantize(image, attr, &res);
|
||||||
|
liq_set_dithering_level(res, dithering);
|
||||||
|
uint8_t* image8bit = (uint8_t*) malloc(size);
|
||||||
|
result = (uint8_t*) malloc(size * 4);
|
||||||
|
liq_write_remapped_image(res, image, image8bit, size);
|
||||||
|
const liq_palette *pal = liq_get_palette(res);
|
||||||
|
// Turn palletted image back into an RGBA image
|
||||||
|
for(int i = 0; i < size; i++) {
|
||||||
|
result[i * 4 + 0] = pal->entries[image8bit[i]].r;
|
||||||
|
result[i * 4 + 1] = pal->entries[image8bit[i]].g;
|
||||||
|
result[i * 4 + 2] = pal->entries[image8bit[i]].b;
|
||||||
|
result[i * 4 + 3] = pal->entries[image8bit[i]].a;
|
||||||
|
}
|
||||||
|
free(image8bit);
|
||||||
|
liq_result_destroy(res);
|
||||||
|
liq_image_destroy(image);
|
||||||
|
liq_attr_destroy(attr);
|
||||||
|
return {
|
||||||
|
val(typed_memory_view(image_width*image_height*4, result)),
|
||||||
|
image_width,
|
||||||
|
image_height
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const liq_color zx_colors[] = {
|
||||||
|
{.a = 255, .r = 0, .g = 0, .b = 0}, // regular black
|
||||||
|
{.a = 255, .r = 0, .g = 0, .b = 215}, // regular blue
|
||||||
|
{.a = 255, .r = 215, .g = 0, .b = 0}, // regular red
|
||||||
|
{.a = 255, .r = 215, .g = 0, .b = 215}, // regular magenta
|
||||||
|
{.a = 255, .r = 0, .g = 215, .b = 0}, // regular green
|
||||||
|
{.a = 255, .r = 0, .g = 215, .b = 215}, // regular cyan
|
||||||
|
{.a = 255, .r = 215, .g = 215, .b = 0}, // regular yellow
|
||||||
|
{.a = 255, .r = 215, .g = 215, .b = 215}, // regular white
|
||||||
|
{.a = 255, .r = 0, .g = 0, .b = 255}, // bright blue
|
||||||
|
{.a = 255, .r = 255, .g = 0, .b = 0}, // bright red
|
||||||
|
{.a = 255, .r = 255, .g = 0, .b = 255}, // bright magenta
|
||||||
|
{.a = 255, .r = 0, .g = 255, .b = 0}, // bright green
|
||||||
|
{.a = 255, .r = 0, .g = 255, .b = 255}, // bright cyan
|
||||||
|
{.a = 255, .r = 255, .g = 255, .b = 0}, // bright yellow
|
||||||
|
{.a = 255, .r = 255, .g = 255, .b = 255} // bright white
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t block[8 * 8 * 4];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ZX has one bit per pixel, but can assign two colours to an 8x8 block. The two colours must
|
||||||
|
* both be 'regular' or 'bright'. Black exists as both regular and bright.
|
||||||
|
*/
|
||||||
|
RawImage zx_quantize(std::string rawimage, int image_width, int image_height, float dithering) {
|
||||||
|
const uint8_t* image_buffer = (uint8_t*) rawimage.c_str();
|
||||||
|
int size = image_width * image_height;
|
||||||
|
int bytes_per_pixel = 4;
|
||||||
|
result = (uint8_t*) malloc(size * bytes_per_pixel);
|
||||||
|
uint8_t* image8bit = (uint8_t*) malloc(8 * 8);
|
||||||
|
|
||||||
|
// For each 8x8 grid
|
||||||
|
for (int block_start_y = 0; block_start_y < image_height; block_start_y += 8) {
|
||||||
|
for (int block_start_x = 0; block_start_x < image_width; block_start_x += 8) {
|
||||||
|
int color_popularity[15] = {0};
|
||||||
|
int block_index = 0;
|
||||||
|
int block_width = 8;
|
||||||
|
int block_height = 8;
|
||||||
|
|
||||||
|
// If the block hangs off the right/bottom of the image dimensions, make it smaller to fit.
|
||||||
|
if (block_start_y + block_height > image_height) {
|
||||||
|
block_height = image_height - block_start_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block_start_x + block_width > image_width) {
|
||||||
|
block_width = image_width - block_start_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each pixel in that block:
|
||||||
|
for (int y = block_start_y; y < block_start_y + block_height; y++) {
|
||||||
|
for (int x = block_start_x; x < block_start_x + block_width; x++) {
|
||||||
|
int pixel_start = (y * image_width * bytes_per_pixel) + (x * bytes_per_pixel);
|
||||||
|
int smallest_distance = INT_MAX;
|
||||||
|
int winning_index = -1;
|
||||||
|
|
||||||
|
// Copy pixel data for quantizing later
|
||||||
|
block[block_index++] = image_buffer[pixel_start];
|
||||||
|
block[block_index++] = image_buffer[pixel_start + 1];
|
||||||
|
block[block_index++] = image_buffer[pixel_start + 2];
|
||||||
|
block[block_index++] = image_buffer[pixel_start + 3];
|
||||||
|
|
||||||
|
// Which zx color is this pixel closest to?
|
||||||
|
for (int color_index = 0; color_index < 15; color_index++) {
|
||||||
|
liq_color color = zx_colors[color_index];
|
||||||
|
|
||||||
|
// Using Euclidean distance. LibQuant has better methods, but it requires conversion to
|
||||||
|
// LAB, so I don't think it's worth it.
|
||||||
|
int distance =
|
||||||
|
pow(color.r - image_buffer[pixel_start + 0], 2) +
|
||||||
|
pow(color.g - image_buffer[pixel_start + 1], 2) +
|
||||||
|
pow(color.b - image_buffer[pixel_start + 2], 2);
|
||||||
|
|
||||||
|
if (distance < smallest_distance) {
|
||||||
|
winning_index = color_index;
|
||||||
|
smallest_distance = distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
color_popularity[winning_index]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the three most popular colours for the block.
|
||||||
|
int first_color_index = 0;
|
||||||
|
int second_color_index = 0;
|
||||||
|
int third_color_index = 0;
|
||||||
|
int highest_popularity = -1;
|
||||||
|
int second_highest_popularity = -1;
|
||||||
|
int third_highest_popularity = -1;
|
||||||
|
|
||||||
|
for (int color_index = 0; color_index < 15; color_index++) {
|
||||||
|
if (color_popularity[color_index] > highest_popularity) {
|
||||||
|
// Store this as the most popular pixel, and demote the current values:
|
||||||
|
third_color_index = second_color_index;
|
||||||
|
third_highest_popularity = second_highest_popularity;
|
||||||
|
second_color_index = first_color_index;
|
||||||
|
second_highest_popularity = highest_popularity;
|
||||||
|
first_color_index = color_index;
|
||||||
|
highest_popularity = color_popularity[color_index];
|
||||||
|
} else if (color_popularity[color_index] > second_highest_popularity) {
|
||||||
|
third_color_index = second_color_index;
|
||||||
|
third_highest_popularity = second_highest_popularity;
|
||||||
|
second_color_index = color_index;
|
||||||
|
second_highest_popularity = color_popularity[color_index];
|
||||||
|
} else if (color_popularity[color_index] > third_highest_popularity) {
|
||||||
|
third_color_index = color_index;
|
||||||
|
third_highest_popularity = color_popularity[color_index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ZX images can't mix bright and regular colours, except black which appears in both.
|
||||||
|
// Resolve any conflict:
|
||||||
|
while (1) {
|
||||||
|
// If either colour is black, there's no conflict to resolve.
|
||||||
|
if (first_color_index != 0 && second_color_index != 0) {
|
||||||
|
if (first_color_index >= 8 && second_color_index < 8) {
|
||||||
|
// Make the second color bright
|
||||||
|
second_color_index = second_color_index + 7;
|
||||||
|
} else if (first_color_index < 8 && second_color_index >= 8) {
|
||||||
|
// Make the second color regular
|
||||||
|
second_color_index = second_color_index - 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If, during conflict resolving, we now have two of the same colour (because we initially
|
||||||
|
// selected the bright & regular version of the same colour), retry again with the third
|
||||||
|
// most popular colour.
|
||||||
|
if (first_color_index == second_color_index) {
|
||||||
|
second_color_index = third_color_index;
|
||||||
|
} else break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quantize
|
||||||
|
attr = liq_attr_create();
|
||||||
|
image = liq_image_create_rgba(attr, block, block_width, block_height, 0);
|
||||||
|
liq_set_max_colors(attr, 2);
|
||||||
|
liq_image_add_fixed_color(image, zx_colors[first_color_index]);
|
||||||
|
liq_image_add_fixed_color(image, zx_colors[second_color_index]);
|
||||||
|
liq_image_quantize(image, attr, &res);
|
||||||
|
liq_set_dithering_level(res, dithering);
|
||||||
|
liq_write_remapped_image(res, image, image8bit, size);
|
||||||
|
const liq_palette *pal = liq_get_palette(res);
|
||||||
|
|
||||||
|
// Turn palletted image back into an RGBA image, and write it into the full size result image.
|
||||||
|
for(int y = 0; y < block_height; y++) {
|
||||||
|
for(int x = 0; x < block_width; x++) {
|
||||||
|
int image8BitPos = y * block_width + x;
|
||||||
|
int resultStartPos = ((block_start_y + y) * bytes_per_pixel * image_width) + ((block_start_x + x) * bytes_per_pixel);
|
||||||
|
result[resultStartPos + 0] = pal->entries[image8bit[image8BitPos]].r;
|
||||||
|
result[resultStartPos + 1] = pal->entries[image8bit[image8BitPos]].g;
|
||||||
|
result[resultStartPos + 2] = pal->entries[image8bit[image8BitPos]].b;
|
||||||
|
result[resultStartPos + 3] = pal->entries[image8bit[image8BitPos]].a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
liq_result_destroy(res);
|
||||||
|
liq_image_destroy(image);
|
||||||
|
liq_attr_destroy(attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(image8bit);
|
||||||
|
return {
|
||||||
|
val(typed_memory_view(image_width*image_height*4, result)),
|
||||||
|
image_width,
|
||||||
|
image_height
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_result() {
|
||||||
|
free(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_BINDINGS(my_module) {
|
||||||
|
class_<RawImage>("RawImage")
|
||||||
|
.property("buffer", &RawImage::buffer)
|
||||||
|
.property("width", &RawImage::width)
|
||||||
|
.property("height", &RawImage::height);
|
||||||
|
|
||||||
|
function("quantize", &quantize);
|
||||||
|
function("zx_quantize", &zx_quantize);
|
||||||
|
function("version", &version);
|
||||||
|
function("free_result", &free_result);
|
||||||
|
}
|
||||||
15
codecs/imagequant/imagequant.d.ts
vendored
Normal file
15
codecs/imagequant/imagequant.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
interface RawImage {
|
||||||
|
buffer: Uint8Array;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface QuantizerModule extends EmscriptenWasm.Module {
|
||||||
|
quantize(data: BufferSource, width: number, height: number, numColors: number, dither: number): RawImage;
|
||||||
|
zx_quantize(data: BufferSource, width: number, height: number, dither: number): RawImage;
|
||||||
|
free_result(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function(opts: EmscriptenWasm.ModuleOpts): QuantizerModule;
|
||||||
|
|
||||||
|
|
||||||
24
codecs/imagequant/imagequant.js
Normal file
24
codecs/imagequant/imagequant.js
Normal file
File diff suppressed because one or more lines are too long
BIN
codecs/imagequant/imagequant.wasm
Normal file
BIN
codecs/imagequant/imagequant.wasm
Normal file
Binary file not shown.
1147
codecs/imagequant/package-lock.json
generated
Normal file
1147
codecs/imagequant/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
codecs/imagequant/package.json
Normal file
13
codecs/imagequant/package.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "imagequant",
|
||||||
|
"scripts": {
|
||||||
|
"install": "napa",
|
||||||
|
"build": "docker run --rm -v $(pwd):/src trzeci/emscripten ./build.sh"
|
||||||
|
},
|
||||||
|
"napa": {
|
||||||
|
"libimagequant": "ImageOptim/libimagequant#2.12.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"napa": "3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,8 +6,6 @@
|
|||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
- Docker
|
- Docker
|
||||||
- Automake
|
|
||||||
- pkg-config
|
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
@@ -19,26 +17,31 @@ See `example.html`
|
|||||||
|
|
||||||
Returns the version of MozJPEG as a number. va.b.c is encoded as 0x0a0b0c
|
Returns the version of MozJPEG as a number. va.b.c is encoded as 0x0a0b0c
|
||||||
|
|
||||||
### `uint8_t* create_buffer(int width, int height)`
|
|
||||||
|
|
||||||
Allocates an RGBA buffer for an image with the given dimension.
|
|
||||||
|
|
||||||
### `void destroy_buffer(uint8_t* p)`
|
|
||||||
|
|
||||||
Frees a buffer created with `create_buffer`.
|
|
||||||
|
|
||||||
### `void encode(uint8_t* image_buffer, int image_width, int image_height, int quality)`
|
|
||||||
|
|
||||||
Encodes the given image with given dimension to JPEG. `quality` is a number between 0 and 100. The higher the number, the better the quality of the encoded image. The result is implicitly stored and can be accessed using the `get_result_*()` functions.
|
|
||||||
|
|
||||||
### `void free_result()`
|
### `void free_result()`
|
||||||
|
|
||||||
Frees the result created by `encode()`.
|
Frees the result created by `encode()`.
|
||||||
|
|
||||||
### `int get_result_pointer()`
|
### `Uint8Array encode(std::string image_in, int image_width, int image_height, MozJpegOptions opts)`
|
||||||
|
|
||||||
Returns the pointer to the start of the buffer holding the encoded data.
|
Encodes the given image with given dimension to JPEG. Options looks like this:
|
||||||
|
|
||||||
### `int get_result_size()`
|
```c++
|
||||||
|
struct MozJpegOptions {
|
||||||
Returns the length of the buffer holding the encoded data.
|
int quality;
|
||||||
|
bool baseline;
|
||||||
|
bool arithmetic;
|
||||||
|
bool progressive;
|
||||||
|
bool optimize_coding;
|
||||||
|
int smoothing;
|
||||||
|
int color_space;
|
||||||
|
int quant_table;
|
||||||
|
bool trellis_multipass;
|
||||||
|
bool trellis_opt_zero;
|
||||||
|
bool trellis_opt_table;
|
||||||
|
int trellis_loops;
|
||||||
|
bool auto_subsample;
|
||||||
|
int chroma_subsample;
|
||||||
|
bool separate_chroma_quality;
|
||||||
|
int chroma_quality;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|||||||
53
codecs/mozjpeg_enc/build.sh
Executable file
53
codecs/mozjpeg_enc/build.sh
Executable file
@@ -0,0 +1,53 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
export OPTIMIZE="-Os"
|
||||||
|
export LDFLAGS="${OPTIMIZE}"
|
||||||
|
export CFLAGS="${OPTIMIZE}"
|
||||||
|
export CPPFLAGS="${OPTIMIZE}"
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
apt-get install -qqy autoconf libtool libpng-dev pkg-config
|
||||||
|
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling mozjpeg"
|
||||||
|
echo "============================================="
|
||||||
|
(
|
||||||
|
cd node_modules/mozjpeg
|
||||||
|
autoreconf -fiv
|
||||||
|
emconfigure ./configure --without-simd
|
||||||
|
emmake make libjpeg.la
|
||||||
|
)
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling mozjpeg done"
|
||||||
|
echo "============================================="
|
||||||
|
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling wasm bindings"
|
||||||
|
echo "============================================="
|
||||||
|
(
|
||||||
|
emcc \
|
||||||
|
--bind \
|
||||||
|
${OPTIMIZE} \
|
||||||
|
-s WASM=1 \
|
||||||
|
-s ALLOW_MEMORY_GROWTH=1 \
|
||||||
|
-s MODULARIZE=1 \
|
||||||
|
-s 'EXPORT_NAME="mozjpeg_enc"' \
|
||||||
|
-I node_modules/mozjpeg \
|
||||||
|
-o ./mozjpeg_enc.js \
|
||||||
|
-Wno-deprecated-register \
|
||||||
|
-Wno-writable-strings \
|
||||||
|
node_modules/mozjpeg/rdswitch.c \
|
||||||
|
-x c++ -std=c++11 \
|
||||||
|
mozjpeg_enc.cpp \
|
||||||
|
node_modules/mozjpeg/.libs/libjpeg.a
|
||||||
|
)
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling wasm bindings done"
|
||||||
|
echo "============================================="
|
||||||
|
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
|
echo "Did you update your docker image?"
|
||||||
|
echo "Run \`docker pull trzeci/emscripten\`"
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<script src='mozjpeg_enc.js'></script>
|
<script src='mozjpeg_enc.js'></script>
|
||||||
<script>
|
<script>
|
||||||
const Module = mozjpeg_enc();
|
const module = mozjpeg_enc();
|
||||||
|
|
||||||
async function loadImage(src) {
|
async function loadImage(src) {
|
||||||
// Load image
|
// Load image
|
||||||
@@ -17,27 +17,27 @@
|
|||||||
return ctx.getImageData(0, 0, img.width, img.height);
|
return ctx.getImageData(0, 0, img.width, img.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
Module.onRuntimeInitialized = async _ => {
|
module.onRuntimeInitialized = async _ => {
|
||||||
const api = {
|
console.log('Version:', module.version().toString(16));
|
||||||
version: Module.cwrap('version', 'number', []),
|
|
||||||
create_buffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
|
|
||||||
destroy_buffer: Module.cwrap('destroy_buffer', '', ['number']),
|
|
||||||
encode: Module.cwrap('encode', '', ['number', 'number', 'number', 'number']),
|
|
||||||
free_result: Module.cwrap('free_result', '', ['number']),
|
|
||||||
get_result_pointer: Module.cwrap('get_result_pointer', 'number', []),
|
|
||||||
get_result_size: Module.cwrap('get_result_size', 'number', []),
|
|
||||||
};
|
|
||||||
console.log('Version:', api.version().toString(16));
|
|
||||||
const image = await loadImage('../example.png');
|
const image = await loadImage('../example.png');
|
||||||
const p = api.create_buffer(image.width, image.height);
|
const result = module.encode(image.data, image.width, image.height, {
|
||||||
Module.HEAP8.set(image.data, p);
|
quality: 75,
|
||||||
api.encode(p, image.width, image.height, 2);
|
baseline: false,
|
||||||
const resultPointer = api.get_result_pointer();
|
arithmetic: false,
|
||||||
const resultSize = api.get_result_size();
|
progressive: true,
|
||||||
const resultView = new Uint8Array(Module.HEAP8.buffer, resultPointer, resultSize);
|
optimize_coding: true,
|
||||||
const result = new Uint8Array(resultView);
|
smoothing: 0,
|
||||||
api.free_result(resultPointer);
|
color_space: 3,
|
||||||
api.destroy_buffer(p);
|
quant_table: 3,
|
||||||
|
trellis_multipass: false,
|
||||||
|
trellis_opt_zero: false,
|
||||||
|
trellis_opt_table: false,
|
||||||
|
trellis_loops: 1,
|
||||||
|
auto_subsample: true,
|
||||||
|
chroma_subsample: 2,
|
||||||
|
separate_chroma_quality: false,
|
||||||
|
chroma_quality: 75,
|
||||||
|
});
|
||||||
|
|
||||||
const blob = new Blob([result], {type: 'image/jpeg'});
|
const blob = new Blob([result], {type: 'image/jpeg'});
|
||||||
const blobURL = URL.createObjectURL(blob);
|
const blobURL = URL.createObjectURL(blob);
|
||||||
|
|||||||
@@ -1,17 +1,40 @@
|
|||||||
#include "emscripten.h"
|
#include <emscripten/bind.h>
|
||||||
|
#include <emscripten/val.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <setjmp.h>
|
#include <setjmp.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "jpeglib.h"
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "jpeglib.h"
|
||||||
|
#include "cdjpeg.h"
|
||||||
|
|
||||||
// MozJPEG doesn’t expose a numeric version, so I have to do some fun C macro hackery to turn it into a string. More details here: https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html
|
using namespace emscripten;
|
||||||
|
|
||||||
|
// MozJPEG doesn’t expose a numeric version, so I have to do some fun C macro hackery to turn it
|
||||||
|
// into a string. More details here: https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html
|
||||||
#define xstr(s) str(s)
|
#define xstr(s) str(s)
|
||||||
#define str(s) #s
|
#define str(s) #s
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
struct MozJpegOptions {
|
||||||
|
int quality;
|
||||||
|
bool baseline;
|
||||||
|
bool arithmetic;
|
||||||
|
bool progressive;
|
||||||
|
bool optimize_coding;
|
||||||
|
int smoothing;
|
||||||
|
int color_space;
|
||||||
|
int quant_table;
|
||||||
|
bool trellis_multipass;
|
||||||
|
bool trellis_opt_zero;
|
||||||
|
bool trellis_opt_table;
|
||||||
|
int trellis_loops;
|
||||||
|
bool auto_subsample;
|
||||||
|
int chroma_subsample;
|
||||||
|
bool separate_chroma_quality;
|
||||||
|
int chroma_quality;
|
||||||
|
};
|
||||||
|
|
||||||
int version() {
|
int version() {
|
||||||
char buffer[] = xstr(MOZJPEG_VERSION);
|
char buffer[] = xstr(MOZJPEG_VERSION);
|
||||||
int version = 0;
|
int version = 0;
|
||||||
@@ -28,27 +51,11 @@ int version() {
|
|||||||
return version;
|
return version;
|
||||||
}
|
}
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
uint8_t* last_result;
|
||||||
uint8_t* create_buffer(int width, int height) {
|
struct jpeg_compress_struct cinfo;
|
||||||
return malloc(width * height * 4 * sizeof(uint8_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
val encode(std::string image_in, int image_width, int image_height, MozJpegOptions opts) {
|
||||||
void destroy_buffer(uint8_t* p) {
|
uint8_t* image_buffer = (uint8_t*) image_in.c_str();
|
||||||
free(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
int result[2];
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
void encode(uint8_t* image_buffer, int image_width, int image_height, int quality) {
|
|
||||||
// Manually convert RGBA data into RGB
|
|
||||||
for(int y = 0; y < image_height; y++) {
|
|
||||||
for(int x = 0; x < image_width; x++) {
|
|
||||||
image_buffer[(y*image_width + x)*3 + 0] = image_buffer[(y*image_width + x)*4 + 0];
|
|
||||||
image_buffer[(y*image_width + x)*3 + 1] = image_buffer[(y*image_width + x)*4 + 1];
|
|
||||||
image_buffer[(y*image_width + x)*3 + 2] = image_buffer[(y*image_width + x)*4 + 2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The code below is basically the `write_JPEG_file` function from
|
// The code below is basically the `write_JPEG_file` function from
|
||||||
// https://github.com/mozilla/mozjpeg/blob/master/example.c
|
// https://github.com/mozilla/mozjpeg/blob/master/example.c
|
||||||
@@ -61,7 +68,6 @@ void encode(uint8_t* image_buffer, int image_width, int image_height, int qualit
|
|||||||
* compression/decompression processes, in existence at once. We refer
|
* compression/decompression processes, in existence at once. We refer
|
||||||
* to any one struct (and its associated working data) as a "JPEG object".
|
* to any one struct (and its associated working data) as a "JPEG object".
|
||||||
*/
|
*/
|
||||||
struct jpeg_compress_struct cinfo;
|
|
||||||
/* This struct represents a JPEG error handler. It is declared separately
|
/* This struct represents a JPEG error handler. It is declared separately
|
||||||
* because applications often want to supply a specialized error handler
|
* because applications often want to supply a specialized error handler
|
||||||
* (see the second half of this file for an example). But here we just
|
* (see the second half of this file for an example). But here we just
|
||||||
@@ -109,18 +115,57 @@ void encode(uint8_t* image_buffer, int image_width, int image_height, int qualit
|
|||||||
*/
|
*/
|
||||||
cinfo.image_width = image_width; /* image width and height, in pixels */
|
cinfo.image_width = image_width; /* image width and height, in pixels */
|
||||||
cinfo.image_height = image_height;
|
cinfo.image_height = image_height;
|
||||||
cinfo.input_components = 3; /* # of color components per pixel */
|
cinfo.input_components = 4; /* # of color components per pixel */
|
||||||
cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
|
cinfo.in_color_space = JCS_EXT_RGBA; /* colorspace of input image */
|
||||||
/* Now use the library's routine to set default compression parameters.
|
/* Now use the library's routine to set default compression parameters.
|
||||||
* (You must set at least cinfo.in_color_space before calling this,
|
* (You must set at least cinfo.in_color_space before calling this,
|
||||||
* since the defaults depend on the source color space.)
|
* since the defaults depend on the source color space.)
|
||||||
*/
|
*/
|
||||||
jpeg_set_defaults(&cinfo);
|
jpeg_set_defaults(&cinfo);
|
||||||
/* Now you can set any non-default parameters you wish to.
|
|
||||||
* Here we just illustrate the use of quality (quantization table) scaling:
|
|
||||||
*/
|
|
||||||
jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
|
|
||||||
|
|
||||||
|
jpeg_set_colorspace(&cinfo, (J_COLOR_SPACE) opts.color_space);
|
||||||
|
|
||||||
|
if (opts.quant_table != -1) {
|
||||||
|
jpeg_c_set_int_param(&cinfo, JINT_BASE_QUANT_TBL_IDX, opts.quant_table);
|
||||||
|
}
|
||||||
|
|
||||||
|
cinfo.optimize_coding = opts.optimize_coding;
|
||||||
|
|
||||||
|
if (opts.arithmetic) {
|
||||||
|
cinfo.arith_code = TRUE;
|
||||||
|
cinfo.optimize_coding = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
cinfo.smoothing_factor = opts.smoothing;
|
||||||
|
|
||||||
|
jpeg_c_set_bool_param(&cinfo, JBOOLEAN_USE_SCANS_IN_TRELLIS, opts.trellis_multipass);
|
||||||
|
jpeg_c_set_bool_param(&cinfo, JBOOLEAN_TRELLIS_EOB_OPT, opts.trellis_opt_zero);
|
||||||
|
jpeg_c_set_bool_param(&cinfo, JBOOLEAN_TRELLIS_Q_OPT, opts.trellis_opt_table);
|
||||||
|
jpeg_c_set_int_param(&cinfo, JINT_TRELLIS_NUM_LOOPS, opts.trellis_loops);
|
||||||
|
|
||||||
|
// A little hacky to build a string for this, but it means we can use set_quality_ratings which
|
||||||
|
// does some useful heuristic stuff.
|
||||||
|
std::string quality_str = std::to_string(opts.quality);
|
||||||
|
|
||||||
|
if (opts.separate_chroma_quality && opts.color_space == JCS_YCbCr) {
|
||||||
|
quality_str += "," + std::to_string(opts.chroma_quality);
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *pqual = quality_str.c_str();
|
||||||
|
|
||||||
|
set_quality_ratings(&cinfo, (char*) pqual, opts.baseline);
|
||||||
|
|
||||||
|
if (!opts.auto_subsample && opts.color_space == JCS_YCbCr) {
|
||||||
|
cinfo.comp_info[0].h_samp_factor = opts.chroma_subsample;
|
||||||
|
cinfo.comp_info[0].v_samp_factor = opts.chroma_subsample;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!opts.baseline && opts.progressive) {
|
||||||
|
jpeg_simple_progression(&cinfo);
|
||||||
|
} else {
|
||||||
|
cinfo.num_scans = 0;
|
||||||
|
cinfo.scan_info = NULL;
|
||||||
|
}
|
||||||
/* Step 4: Start compressor */
|
/* Step 4: Start compressor */
|
||||||
|
|
||||||
/* TRUE ensures that we will write a complete interchange-JPEG file.
|
/* TRUE ensures that we will write a complete interchange-JPEG file.
|
||||||
@@ -136,7 +181,7 @@ void encode(uint8_t* image_buffer, int image_width, int image_height, int qualit
|
|||||||
* To keep things simple, we pass one scanline per call; you can pass
|
* To keep things simple, we pass one scanline per call; you can pass
|
||||||
* more if you wish, though.
|
* more if you wish, though.
|
||||||
*/
|
*/
|
||||||
row_stride = image_width * 3; /* JSAMPLEs per row in image_buffer */
|
row_stride = image_width * 4; /* JSAMPLEs per row in image_buffer */
|
||||||
|
|
||||||
while (cinfo.next_scanline < cinfo.image_height) {
|
while (cinfo.next_scanline < cinfo.image_height) {
|
||||||
/* jpeg_write_scanlines expects an array of pointers to scanlines.
|
/* jpeg_write_scanlines expects an array of pointers to scanlines.
|
||||||
@@ -152,27 +197,38 @@ void encode(uint8_t* image_buffer, int image_width, int image_height, int qualit
|
|||||||
jpeg_finish_compress(&cinfo);
|
jpeg_finish_compress(&cinfo);
|
||||||
/* Step 7: release JPEG compression object */
|
/* Step 7: release JPEG compression object */
|
||||||
|
|
||||||
result[0] = (int)output;
|
last_result = output;
|
||||||
result[1] = size;
|
|
||||||
|
|
||||||
/* This is an important step since it will release a good deal of memory. */
|
|
||||||
jpeg_destroy_compress(&cinfo);
|
|
||||||
|
|
||||||
/* And we're done! */
|
/* And we're done! */
|
||||||
|
return val(typed_memory_view(size, output));
|
||||||
}
|
}
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
void free_result() {
|
void free_result() {
|
||||||
free(result[0]); // not sure if this is right with mozjpeg
|
/* This is an important step since it will release a good deal of memory. */
|
||||||
|
jpeg_destroy_compress(&cinfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
EMSCRIPTEN_BINDINGS(my_module) {
|
||||||
int get_result_pointer() {
|
value_object<MozJpegOptions>("MozJpegOptions")
|
||||||
return result[0];
|
.field("quality", &MozJpegOptions::quality)
|
||||||
}
|
.field("baseline", &MozJpegOptions::baseline)
|
||||||
|
.field("arithmetic", &MozJpegOptions::arithmetic)
|
||||||
|
.field("progressive", &MozJpegOptions::progressive)
|
||||||
|
.field("optimize_coding", &MozJpegOptions::optimize_coding)
|
||||||
|
.field("smoothing", &MozJpegOptions::smoothing)
|
||||||
|
.field("color_space", &MozJpegOptions::color_space)
|
||||||
|
.field("quant_table", &MozJpegOptions::quant_table)
|
||||||
|
.field("trellis_multipass", &MozJpegOptions::trellis_multipass)
|
||||||
|
.field("trellis_opt_zero", &MozJpegOptions::trellis_opt_zero)
|
||||||
|
.field("trellis_opt_table", &MozJpegOptions::trellis_opt_table)
|
||||||
|
.field("trellis_loops", &MozJpegOptions::trellis_loops)
|
||||||
|
.field("chroma_subsample", &MozJpegOptions::chroma_subsample)
|
||||||
|
.field("auto_subsample", &MozJpegOptions::auto_subsample)
|
||||||
|
.field("separate_chroma_quality", &MozJpegOptions::separate_chroma_quality)
|
||||||
|
.field("chroma_quality", &MozJpegOptions::chroma_quality)
|
||||||
|
;
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
function("version", &version);
|
||||||
int get_result_size() {
|
function("encode", &encode);
|
||||||
return result[1];
|
function("free_result", &free_result);
|
||||||
}
|
}
|
||||||
|
|
||||||
9
codecs/mozjpeg_enc/mozjpeg_enc.d.ts
vendored
9
codecs/mozjpeg_enc/mozjpeg_enc.d.ts
vendored
@@ -1 +1,8 @@
|
|||||||
export default function(opts: EmscriptenWasm.ModuleOpts): EmscriptenWasm.Module;
|
import { EncodeOptions } from '../../src/codecs/mozjpeg/encoder-meta';
|
||||||
|
|
||||||
|
interface MozJPEGModule extends EmscriptenWasm.Module {
|
||||||
|
encode(data: BufferSource, width: number, height: number, options: EncodeOptions): Uint8Array;
|
||||||
|
free_result(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function(opts: EmscriptenWasm.ModuleOpts): MozJPEGModule;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -2,14 +2,12 @@
|
|||||||
"name": "mozjpeg_enc",
|
"name": "mozjpeg_enc",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "napa",
|
"install": "napa",
|
||||||
"build": "npm run build:library && npm run build:wasm",
|
"build": "docker run --rm -v $(pwd):/src trzeci/emscripten ./build.sh"
|
||||||
"build:library": "cd node_modules/mozjpeg && autoreconf -fiv && docker run --rm -v $(pwd):/src trzeci/emscripten emconfigure ./configure --without-simd && docker run --rm -v $(pwd):/src trzeci/emscripten emmake make libjpeg.la",
|
|
||||||
"build:wasm": "docker run --rm -v $(pwd):/src trzeci/emscripten emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"cwrap\"]' -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s 'EXPORT_NAME=\"mozjpeg_enc\"' -I node_modules/mozjpeg -o ./mozjpeg_enc.js mozjpeg_enc.c node_modules/mozjpeg/.libs/libjpeg.a"
|
|
||||||
},
|
},
|
||||||
"napa": {
|
"napa": {
|
||||||
"mozjpeg": "mozilla/mozjpeg#v3.3.1"
|
"mozjpeg": "mozilla/mozjpeg#v3.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"napa": "^3.0.0"
|
"napa": "3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
2
codecs/optipng/.gitignore
vendored
Normal file
2
codecs/optipng/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
build/
|
||||||
|
*.o
|
||||||
26
codecs/optipng/README.md
Normal file
26
codecs/optipng/README.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# OptiPNG
|
||||||
|
|
||||||
|
- Source: <http://optipng.sourceforge.net/>
|
||||||
|
- Version: v0.7.7
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Docker
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
See `example.html`
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### `int version()`
|
||||||
|
|
||||||
|
Returns the version of optipng as a number. va.b.c is encoded as 0x0a0b0c
|
||||||
|
|
||||||
|
### `ArrayBuffer compress(std::string buffer, {level})`;
|
||||||
|
|
||||||
|
`compress` will re-compress the given PNG image via `buffer`. `level` is a number between 0 and 7.
|
||||||
|
|
||||||
|
### `void free_result()`
|
||||||
|
|
||||||
|
Frees the result created by `compress()`.
|
||||||
87
codecs/optipng/build.sh
Executable file
87
codecs/optipng/build.sh
Executable file
@@ -0,0 +1,87 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
export OPTIMIZE="-Os"
|
||||||
|
export PREFIX="/src/build"
|
||||||
|
export CFLAGS="${OPTIMIZE} -I${PREFIX}/include/"
|
||||||
|
export CPPFLAGS="${OPTIMIZE} -I${PREFIX}/include/"
|
||||||
|
export LDFLAGS="${OPTIMIZE} -L${PREFIX}/lib/"
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
apt-get install -qqy autoconf libtool
|
||||||
|
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling zlib"
|
||||||
|
echo "============================================="
|
||||||
|
test -n "$SKIP_ZLIB" || (
|
||||||
|
cd node_modules/zlib
|
||||||
|
emconfigure ./configure --prefix=${PREFIX}/
|
||||||
|
emmake make
|
||||||
|
emmake make install
|
||||||
|
)
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling zlib done"
|
||||||
|
echo "============================================="
|
||||||
|
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling libpng"
|
||||||
|
echo "============================================="
|
||||||
|
test -n "$SKIP_LIBPNG" || (
|
||||||
|
cd node_modules/libpng
|
||||||
|
autoreconf -i
|
||||||
|
emconfigure ./configure --with-zlib-prefix=${PREFIX}/ --prefix=${PREFIX}/
|
||||||
|
emmake make
|
||||||
|
emmake make install
|
||||||
|
)
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling libpng done"
|
||||||
|
echo "============================================="
|
||||||
|
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling optipng"
|
||||||
|
echo "============================================="
|
||||||
|
(
|
||||||
|
emcc \
|
||||||
|
${OPTIMIZE} \
|
||||||
|
-Wno-implicit-function-declaration \
|
||||||
|
-I ${PREFIX}/include \
|
||||||
|
-I node_modules/optipng/src/opngreduc \
|
||||||
|
-I node_modules/optipng/src/pngxtern \
|
||||||
|
-I node_modules/optipng/src/cexcept \
|
||||||
|
-I node_modules/optipng/src/gifread \
|
||||||
|
-I node_modules/optipng/src/pnmio \
|
||||||
|
-I node_modules/optipng/src/minitiff \
|
||||||
|
--std=c99 -c \
|
||||||
|
node_modules/optipng/src/opngreduc/*.c \
|
||||||
|
node_modules/optipng/src/pngxtern/*.c \
|
||||||
|
node_modules/optipng/src/gifread/*.c \
|
||||||
|
node_modules/optipng/src/minitiff/*.c \
|
||||||
|
node_modules/optipng/src/pnmio/*.c \
|
||||||
|
node_modules/optipng/src/optipng/*.c
|
||||||
|
|
||||||
|
emcc \
|
||||||
|
--bind \
|
||||||
|
${OPTIMIZE} \
|
||||||
|
-s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s 'EXPORT_NAME="optipng"' \
|
||||||
|
-I ${PREFIX}/include \
|
||||||
|
-I node_modules/optipng/src/opngreduc \
|
||||||
|
-I node_modules/optipng/src/pngxtern \
|
||||||
|
-I node_modules/optipng/src/cexcept \
|
||||||
|
-I node_modules/optipng/src/gifread \
|
||||||
|
-I node_modules/optipng/src/pnmio \
|
||||||
|
-I node_modules/optipng/src/minitiff \
|
||||||
|
-o "optipng.js" \
|
||||||
|
--std=c++11 \
|
||||||
|
optipng.cpp \
|
||||||
|
*.o \
|
||||||
|
${PREFIX}/lib/libz.so ${PREFIX}/lib/libpng.a
|
||||||
|
)
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling optipng done"
|
||||||
|
echo "============================================="
|
||||||
|
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
|
echo "Did you update your docker image?"
|
||||||
|
echo "Run \`docker pull trzeci/emscripten\`"
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
19
codecs/optipng/example.html
Normal file
19
codecs/optipng/example.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<script src='optipng.js'></script>
|
||||||
|
<script>
|
||||||
|
const Module = optipng();
|
||||||
|
|
||||||
|
Module.onRuntimeInitialized = async _ => {
|
||||||
|
console.log('Version:', Module.version().toString(16));
|
||||||
|
const image = await fetch('../example_palette.png').then(r => r.arrayBuffer());
|
||||||
|
const newImage = Module.compress(image, {level: 3});
|
||||||
|
console.log('done');
|
||||||
|
Module.free_result();
|
||||||
|
|
||||||
|
console.log(`Old size: ${image.byteLength}, new size: ${newImage.byteLength} (${newImage.byteLength/image.byteLength*100}%)`);
|
||||||
|
const blobURL = URL.createObjectURL(new Blob([newImage], {type: 'image/png'}));
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = blobURL;
|
||||||
|
document.body.appendChild(img);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
53
codecs/optipng/optipng.cpp
Normal file
53
codecs/optipng/optipng.cpp
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#include "emscripten/bind.h"
|
||||||
|
#include "emscripten/val.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
using namespace emscripten;
|
||||||
|
|
||||||
|
extern "C" int main(int argc, char *argv[]);
|
||||||
|
|
||||||
|
int version() {
|
||||||
|
// FIXME (@surma): Haven’t found a version in optipng :(
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct OptiPngOpts {
|
||||||
|
int level;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t* result;
|
||||||
|
val compress(std::string png, OptiPngOpts opts) {
|
||||||
|
remove("input.png");
|
||||||
|
remove("output.png");
|
||||||
|
FILE* infile = fopen("input.png", "wb");
|
||||||
|
fwrite(png.c_str(), png.length(), 1, infile);
|
||||||
|
fflush(infile);
|
||||||
|
fclose(infile);
|
||||||
|
|
||||||
|
char optlevel[8];
|
||||||
|
sprintf(&optlevel[0], "-o%d", opts.level);
|
||||||
|
char* args[] = {"optipng", optlevel, "-out", "output.png", "input.png"};
|
||||||
|
main(5, args);
|
||||||
|
|
||||||
|
FILE *outfile = fopen("output.png", "rb");
|
||||||
|
fseek(outfile, 0, SEEK_END);
|
||||||
|
int fsize = ftell(outfile);
|
||||||
|
result = (uint8_t*) malloc(fsize);
|
||||||
|
fseek(outfile, 0, SEEK_SET);
|
||||||
|
fread(result, fsize, 1, outfile);
|
||||||
|
return val(typed_memory_view(fsize, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_result() {
|
||||||
|
free(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_BINDINGS(my_module) {
|
||||||
|
value_object<OptiPngOpts>("OptiPngOpts")
|
||||||
|
.field("level", &OptiPngOpts::level);
|
||||||
|
|
||||||
|
function("version", &version);
|
||||||
|
function("compress", &compress);
|
||||||
|
function("free_result", &free_result);
|
||||||
|
}
|
||||||
10
codecs/optipng/optipng.d.ts
vendored
Normal file
10
codecs/optipng/optipng.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import {EncodeOptions} from "src/codecs/optipng/encoder";
|
||||||
|
|
||||||
|
export interface OptiPngModule extends EmscriptenWasm.Module {
|
||||||
|
compress(data: BufferSource, opts: EncodeOptions): Uint8Array;
|
||||||
|
free_result(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function(opts: EmscriptenWasm.ModuleOpts): OptiPngModule;
|
||||||
|
|
||||||
|
|
||||||
24
codecs/optipng/optipng.js
Normal file
24
codecs/optipng/optipng.js
Normal file
File diff suppressed because one or more lines are too long
BIN
codecs/optipng/optipng.wasm
Normal file
BIN
codecs/optipng/optipng.wasm
Normal file
Binary file not shown.
1457
codecs/optipng/package-lock.json
generated
Normal file
1457
codecs/optipng/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
codecs/optipng/package.json
Normal file
22
codecs/optipng/package.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "optipng",
|
||||||
|
"scripts": {
|
||||||
|
"install": "tar-dependency install && napa",
|
||||||
|
"build": "npm run build:wasm",
|
||||||
|
"build:wasm": "docker run --rm -v $(pwd):/src -e SKIP_ZLIB=\"${SKIP_ZLIB}\" -e SKIP_LIBPNG=\"${SKIP_LIBPNG}\" trzeci/emscripten ./build.sh"
|
||||||
|
},
|
||||||
|
"tarDependencies": {
|
||||||
|
"node_modules/optipng": {
|
||||||
|
"url": "https://netcologne.dl.sourceforge.net/project/optipng/OptiPNG/optipng-0.7.7/optipng-0.7.7.tar.gz",
|
||||||
|
"strip": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"napa": {
|
||||||
|
"libpng": "emscripten-ports/libpng",
|
||||||
|
"zlib": "emscripten-ports/zlib"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"napa": "3.0.0",
|
||||||
|
"tar-dependency": "0.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
6
codecs/resize/.gitignore
vendored
Normal file
6
codecs/resize/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
**/*.rs.bk
|
||||||
|
target
|
||||||
|
Cargo.lock
|
||||||
|
bin/
|
||||||
|
pkg/README.md
|
||||||
|
lut.inc
|
||||||
37
codecs/resize/Cargo.toml
Normal file
37
codecs/resize/Cargo.toml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
[package]
|
||||||
|
name = "resize"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Surma <surma@surma.link>"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
#crate-type = ["cdylib", "rlib"]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["console_error_panic_hook", "wee_alloc"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cfg-if = "0.1.2"
|
||||||
|
wasm-bindgen = "0.2.38"
|
||||||
|
resize = "0.3.0"
|
||||||
|
|
||||||
|
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||||
|
# logging them with `console.error`. This is great for development, but requires
|
||||||
|
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||||
|
# code size when deploying.
|
||||||
|
console_error_panic_hook = { version = "0.1.1", optional = true }
|
||||||
|
|
||||||
|
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
|
||||||
|
# compared to the default allocator's ~10K. It is slower than the default
|
||||||
|
# allocator, however.
|
||||||
|
#
|
||||||
|
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
|
||||||
|
wee_alloc = { version = "0.4.2", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
wasm-bindgen-test = "0.2"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
# Tell `rustc` to optimize for small code size.
|
||||||
|
opt-level = "s"
|
||||||
|
lto = true
|
||||||
9
codecs/resize/Dockerfile
Normal file
9
codecs/resize/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
FROM rust
|
||||||
|
RUN rustup target add wasm32-unknown-unknown
|
||||||
|
RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||||
|
|
||||||
|
RUN mkdir /opt/wabt && \
|
||||||
|
curl -L https://github.com/WebAssembly/wabt/releases/download/1.0.11/wabt-1.0.11-linux.tar.gz | tar -xzf - -C /opt/wabt --strip 1
|
||||||
|
|
||||||
|
ENV PATH="/opt/wabt:${PATH}"
|
||||||
|
WORKDIR /src
|
||||||
23
codecs/resize/build.rs
Normal file
23
codecs/resize/build.rs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
include!("./src/srgb.rs");
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
fn main() -> std::io::Result<()> {
|
||||||
|
let mut srgb_to_linear_lut = String::from("static SRGB_TO_LINEAR_LUT: [f32; 256] = [");
|
||||||
|
let mut linear_to_srgb_lut = String::from("static LINEAR_TO_SRGB_LUT: [f32; 256] = [");
|
||||||
|
for i in 0..256 {
|
||||||
|
srgb_to_linear_lut.push_str(&format!("{0:.7}", srgb_to_linear((i as f32) / 255.0)));
|
||||||
|
srgb_to_linear_lut.push_str(",");
|
||||||
|
linear_to_srgb_lut.push_str(&format!("{0:.7}", linear_to_srgb((i as f32) / 255.0)));
|
||||||
|
linear_to_srgb_lut.push_str(",");
|
||||||
|
}
|
||||||
|
srgb_to_linear_lut.pop().unwrap();
|
||||||
|
linear_to_srgb_lut.pop().unwrap();
|
||||||
|
srgb_to_linear_lut.push_str("];");
|
||||||
|
linear_to_srgb_lut.push_str("];");
|
||||||
|
|
||||||
|
let mut file = std::fs::File::create("src/lut.inc")?;
|
||||||
|
file.write_all(srgb_to_linear_lut.as_bytes())?;
|
||||||
|
file.write_all(linear_to_srgb_lut.as_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
22
codecs/resize/build.sh
Executable file
22
codecs/resize/build.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling wasm"
|
||||||
|
echo "============================================="
|
||||||
|
(
|
||||||
|
wasm-pack build
|
||||||
|
wasm-strip pkg/resize_bg.wasm
|
||||||
|
rm pkg/.gitignore
|
||||||
|
)
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling wasm done"
|
||||||
|
echo "============================================="
|
||||||
|
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
|
echo "Did you update your docker image?"
|
||||||
|
echo "Run \`docker pull ubuntu\`"
|
||||||
|
echo "Run \`docker pull rust\`"
|
||||||
|
echo "Run \`docker build -t squoosh-resize .\`"
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
4
codecs/resize/package-lock.json
generated
Normal file
4
codecs/resize/package-lock.json
generated
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "resize",
|
||||||
|
"lockfileVersion": 1
|
||||||
|
}
|
||||||
7
codecs/resize/package.json
Normal file
7
codecs/resize/package.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "resize",
|
||||||
|
"scripts": {
|
||||||
|
"build:image": "docker build -t squoosh-resize .",
|
||||||
|
"build": "docker run --rm -v $(pwd):/src squoosh-resize ./build.sh"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
codecs/resize/pkg/package.json
Normal file
15
codecs/resize/pkg/package.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "resize",
|
||||||
|
"collaborators": [
|
||||||
|
"Surma <surma@surma.link>"
|
||||||
|
],
|
||||||
|
"version": "0.1.0",
|
||||||
|
"files": [
|
||||||
|
"resize_bg.wasm",
|
||||||
|
"resize.js",
|
||||||
|
"resize.d.ts"
|
||||||
|
],
|
||||||
|
"module": "resize.js",
|
||||||
|
"types": "resize.d.ts",
|
||||||
|
"sideEffects": "false"
|
||||||
|
}
|
||||||
13
codecs/resize/pkg/resize.d.ts
vendored
Normal file
13
codecs/resize/pkg/resize.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
/**
|
||||||
|
* @param {Uint8Array} input_image
|
||||||
|
* @param {number} input_width
|
||||||
|
* @param {number} input_height
|
||||||
|
* @param {number} output_width
|
||||||
|
* @param {number} output_height
|
||||||
|
* @param {number} typ_idx
|
||||||
|
* @param {boolean} premultiply
|
||||||
|
* @param {boolean} color_space_conversion
|
||||||
|
* @returns {Uint8Array}
|
||||||
|
*/
|
||||||
|
export function resize(input_image: Uint8Array, input_width: number, input_height: number, output_width: number, output_height: number, typ_idx: number, premultiply: boolean, color_space_conversion: boolean): Uint8Array;
|
||||||
50
codecs/resize/pkg/resize.js
Normal file
50
codecs/resize/pkg/resize.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import * as wasm from './resize_bg.wasm';
|
||||||
|
|
||||||
|
let cachegetUint8Memory = null;
|
||||||
|
function getUint8Memory() {
|
||||||
|
if (cachegetUint8Memory === null || cachegetUint8Memory.buffer !== wasm.memory.buffer) {
|
||||||
|
cachegetUint8Memory = new Uint8Array(wasm.memory.buffer);
|
||||||
|
}
|
||||||
|
return cachegetUint8Memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
let WASM_VECTOR_LEN = 0;
|
||||||
|
|
||||||
|
function passArray8ToWasm(arg) {
|
||||||
|
const ptr = wasm.__wbindgen_malloc(arg.length * 1);
|
||||||
|
getUint8Memory().set(arg, ptr / 1);
|
||||||
|
WASM_VECTOR_LEN = arg.length;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachegetInt32Memory = null;
|
||||||
|
function getInt32Memory() {
|
||||||
|
if (cachegetInt32Memory === null || cachegetInt32Memory.buffer !== wasm.memory.buffer) {
|
||||||
|
cachegetInt32Memory = new Int32Array(wasm.memory.buffer);
|
||||||
|
}
|
||||||
|
return cachegetInt32Memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getArrayU8FromWasm(ptr, len) {
|
||||||
|
return getUint8Memory().subarray(ptr / 1, ptr / 1 + len);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {Uint8Array} input_image
|
||||||
|
* @param {number} input_width
|
||||||
|
* @param {number} input_height
|
||||||
|
* @param {number} output_width
|
||||||
|
* @param {number} output_height
|
||||||
|
* @param {number} typ_idx
|
||||||
|
* @param {boolean} premultiply
|
||||||
|
* @param {boolean} color_space_conversion
|
||||||
|
* @returns {Uint8Array}
|
||||||
|
*/
|
||||||
|
export function resize(input_image, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion) {
|
||||||
|
const retptr = 8;
|
||||||
|
const ret = wasm.resize(retptr, passArray8ToWasm(input_image), WASM_VECTOR_LEN, input_width, input_height, output_width, output_height, typ_idx, premultiply, color_space_conversion);
|
||||||
|
const memi32 = getInt32Memory();
|
||||||
|
const v0 = getArrayU8FromWasm(memi32[retptr / 4 + 0], memi32[retptr / 4 + 1]).slice();
|
||||||
|
wasm.__wbindgen_free(memi32[retptr / 4 + 0], memi32[retptr / 4 + 1] * 1);
|
||||||
|
return v0;
|
||||||
|
}
|
||||||
|
|
||||||
5
codecs/resize/pkg/resize_bg.d.ts
vendored
Normal file
5
codecs/resize/pkg/resize_bg.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/* tslint:disable */
|
||||||
|
export const memory: WebAssembly.Memory;
|
||||||
|
export function __wbindgen_malloc(a: number): number;
|
||||||
|
export function __wbindgen_free(a: number, b: number): void;
|
||||||
|
export function resize(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number): void;
|
||||||
BIN
codecs/resize/pkg/resize_bg.wasm
Normal file
BIN
codecs/resize/pkg/resize_bg.wasm
Normal file
Binary file not shown.
121
codecs/resize/src/lib.rs
Normal file
121
codecs/resize/src/lib.rs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
extern crate cfg_if;
|
||||||
|
extern crate resize;
|
||||||
|
extern crate wasm_bindgen;
|
||||||
|
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
use cfg_if::cfg_if;
|
||||||
|
use resize::Pixel::RGBA;
|
||||||
|
use resize::Type;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
mod srgb;
|
||||||
|
use srgb::Clamp;
|
||||||
|
|
||||||
|
cfg_if! {
|
||||||
|
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
|
||||||
|
// allocator.
|
||||||
|
if #[cfg(feature = "wee_alloc")] {
|
||||||
|
extern crate wee_alloc;
|
||||||
|
#[global_allocator]
|
||||||
|
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
include!("./lut.inc");
|
||||||
|
|
||||||
|
// If `with_space_conversion` is true, this function returns 2 functions that
|
||||||
|
// convert from sRGB to linear RGB and vice versa. If `with_space_conversion` is
|
||||||
|
// false, the 2 functions returned do nothing.
|
||||||
|
fn converter_funcs(with_space_conversion: bool) -> ((fn(u8) -> f32), (fn(f32) -> u8)) {
|
||||||
|
if with_space_conversion {
|
||||||
|
(
|
||||||
|
|v| SRGB_TO_LINEAR_LUT[v as usize] * 255.0,
|
||||||
|
|v| (LINEAR_TO_SRGB_LUT[v as usize] * 255.0) as u8,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(|v| v as f32, |v| v as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If `with_alpha_premultiplication` is true, this function returns a function
|
||||||
|
// that premultiply the alpha channel with the given channel value and another
|
||||||
|
// function that reverses that process. If `with_alpha_premultiplication` is
|
||||||
|
// false, the functions just return the channel value.
|
||||||
|
fn alpha_multiplier_funcs(
|
||||||
|
with_alpha_premultiplication: bool,
|
||||||
|
) -> ((fn(f32, u8) -> u8), (fn(u8, u8) -> f32)) {
|
||||||
|
if with_alpha_premultiplication {
|
||||||
|
(
|
||||||
|
|v, a| (v * (a as f32) / 255.0) as u8,
|
||||||
|
|v, a| (v as f32) * 255.0 / (a as f32).clamp(0.0, 255.0),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(|v, _a| v as u8, |v, _a| v as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn resize(
|
||||||
|
mut input_image: Vec<u8>,
|
||||||
|
input_width: usize,
|
||||||
|
input_height: usize,
|
||||||
|
output_width: usize,
|
||||||
|
output_height: usize,
|
||||||
|
typ_idx: usize,
|
||||||
|
premultiply: bool,
|
||||||
|
color_space_conversion: bool,
|
||||||
|
) -> Vec<u8> {
|
||||||
|
let typ = match typ_idx {
|
||||||
|
0 => Type::Triangle,
|
||||||
|
1 => Type::Catrom,
|
||||||
|
2 => Type::Mitchell,
|
||||||
|
3 => Type::Lanczos3,
|
||||||
|
_ => panic!("Nope"),
|
||||||
|
};
|
||||||
|
let num_input_pixels = input_width * input_height;
|
||||||
|
let num_output_pixels = output_width * output_height;
|
||||||
|
|
||||||
|
let (to_linear, to_color_space) = converter_funcs(color_space_conversion);
|
||||||
|
let (premultiplier, demultiplier) = alpha_multiplier_funcs(premultiply);
|
||||||
|
|
||||||
|
// If both options are false, there is no preprocessing on the pixel valus
|
||||||
|
// and we can skip the loop.
|
||||||
|
if premultiply || color_space_conversion {
|
||||||
|
for i in 0..num_input_pixels {
|
||||||
|
for j in 0..3 {
|
||||||
|
input_image[4 * i + j] =
|
||||||
|
premultiplier(to_linear(input_image[4 * i + j]), input_image[4 * i + 3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut resizer = resize::new(
|
||||||
|
input_width,
|
||||||
|
input_height,
|
||||||
|
output_width,
|
||||||
|
output_height,
|
||||||
|
RGBA,
|
||||||
|
typ,
|
||||||
|
);
|
||||||
|
let mut output_image = Vec::<u8>::with_capacity(num_output_pixels * 4);
|
||||||
|
output_image.resize(num_output_pixels * 4, 0);
|
||||||
|
resizer.resize(input_image.as_slice(), output_image.as_mut_slice());
|
||||||
|
|
||||||
|
if premultiply || color_space_conversion {
|
||||||
|
for i in 0..num_output_pixels {
|
||||||
|
for j in 0..3 {
|
||||||
|
// We don’t need to worry about division by zero, as division by zero
|
||||||
|
// is well-defined on floats to return ±Inf. ±Inf is converted to 0
|
||||||
|
// when casting to integers.
|
||||||
|
output_image[4 * i + j] = to_color_space(demultiplier(
|
||||||
|
output_image[4 * i + j],
|
||||||
|
output_image[4 * i + 3],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output_image;
|
||||||
|
}
|
||||||
29
codecs/resize/src/srgb.rs
Normal file
29
codecs/resize/src/srgb.rs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
pub trait Clamp: std::cmp::PartialOrd + Sized {
|
||||||
|
fn clamp(self, min: Self, max: Self) -> Self {
|
||||||
|
if self.lt(&min) {
|
||||||
|
min
|
||||||
|
} else if self.gt(&max) {
|
||||||
|
max
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clamp for f32 {}
|
||||||
|
|
||||||
|
pub fn srgb_to_linear(v: f32) -> f32 {
|
||||||
|
if v < 0.04045 {
|
||||||
|
v / 12.92
|
||||||
|
} else {
|
||||||
|
((v + 0.055) / 1.055).powf(2.4).clamp(0.0, 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn linear_to_srgb(v: f32) -> f32 {
|
||||||
|
if v < 0.0031308 {
|
||||||
|
v * 12.92
|
||||||
|
} else {
|
||||||
|
(1.055 * v.powf(1.0 / 2.4) - 0.055).clamp(0.0, 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
17
codecs/resize/src/utils.rs
Normal file
17
codecs/resize/src/utils.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use cfg_if::cfg_if;
|
||||||
|
|
||||||
|
cfg_if! {
|
||||||
|
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||||
|
// `set_panic_hook` function at least once during initialization, and then
|
||||||
|
// we will get better error messages if our code ever panics.
|
||||||
|
//
|
||||||
|
// For more details see
|
||||||
|
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||||
|
if #[cfg(feature = "console_error_panic_hook")] {
|
||||||
|
extern crate console_error_panic_hook;
|
||||||
|
pub use self::console_error_panic_hook::set_once as set_panic_hook;
|
||||||
|
} else {
|
||||||
|
#[inline]
|
||||||
|
pub fn set_panic_hook() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
codecs/rotate/.gitignore
vendored
Normal file
2
codecs/rotate/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
target
|
||||||
|
Cargo.lock
|
||||||
14
codecs/rotate/Cargo.toml
Normal file
14
codecs/rotate/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "rotate"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Surma <surma@google.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "rotate"
|
||||||
|
path = "rotate.rs"
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
opt-level = "s"
|
||||||
8
codecs/rotate/Dockerfile
Normal file
8
codecs/rotate/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
FROM rust
|
||||||
|
RUN rustup target add wasm32-unknown-unknown
|
||||||
|
|
||||||
|
RUN mkdir /opt/wabt && \
|
||||||
|
curl -L https://github.com/WebAssembly/wabt/releases/download/1.0.11/wabt-1.0.11-linux.tar.gz | tar -xzf - -C /opt/wabt --strip 1
|
||||||
|
|
||||||
|
ENV PATH="/opt/wabt:${PATH}"
|
||||||
|
WORKDIR /src
|
||||||
45
codecs/rotate/benchmark.js
Normal file
45
codecs/rotate/benchmark.js
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// THIS IS NOT A NODE SCRIPT
|
||||||
|
// This is a d8 script. Please install jsvu[1] and install v8.
|
||||||
|
// Then run `npm run --silent benchmark`.
|
||||||
|
// [1]: https://github.com/GoogleChromeLabs/jsvu
|
||||||
|
async function init() {
|
||||||
|
// Adjustable constants.
|
||||||
|
const imageDimensions = 4096;
|
||||||
|
const iterations = new Array(100);
|
||||||
|
|
||||||
|
// Constants. Don’t change.
|
||||||
|
const imageByteSize = imageDimensions * imageDimensions * 4;
|
||||||
|
const wasmPageSize = 64 * 1024;
|
||||||
|
|
||||||
|
const buffer = readbuffer("rotate.wasm");
|
||||||
|
const { instance } = await WebAssembly.instantiate(buffer);
|
||||||
|
|
||||||
|
const pagesAvailable = Math.floor(
|
||||||
|
instance.exports.memory.buffer.byteLength / wasmPageSize
|
||||||
|
);
|
||||||
|
const pagesNeeded = Math.floor((imageByteSize * 2 + 4) / wasmPageSize) + 1;
|
||||||
|
const additionalPagesNeeded = pagesNeeded - pagesAvailable;
|
||||||
|
if (additionalPagesNeeded > 0) {
|
||||||
|
instance.exports.memory.grow(additionalPagesNeeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
[0, 90, 180, 270].forEach(rotation => {
|
||||||
|
print(`\n${rotation} degrees`);
|
||||||
|
print(`==============================`);
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
const start = Date.now();
|
||||||
|
instance.exports.rotate(imageDimensions, imageDimensions, rotation);
|
||||||
|
iterations[i] = Date.now() - start;
|
||||||
|
}
|
||||||
|
const average = iterations.reduce((sum, c) => sum + c) / iterations.length;
|
||||||
|
const stddev = Math.sqrt(
|
||||||
|
iterations
|
||||||
|
.map(i => Math.pow(i - average, 2))
|
||||||
|
.reduce((sum, c) => sum + c) / iterations.length
|
||||||
|
);
|
||||||
|
print(`n = ${iterations.length}`);
|
||||||
|
print(`Average: ${average}`);
|
||||||
|
print(`StdDev: ${stddev}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
init().catch(e => console.error(e.stack));
|
||||||
24
codecs/rotate/build.sh
Executable file
24
codecs/rotate/build.sh
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling wasm"
|
||||||
|
echo "============================================="
|
||||||
|
(
|
||||||
|
cargo build \
|
||||||
|
--target wasm32-unknown-unknown \
|
||||||
|
--release
|
||||||
|
cp target/wasm32-unknown-unknown/release/rotate.wasm .
|
||||||
|
wasm-strip rotate.wasm
|
||||||
|
)
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling wasm done"
|
||||||
|
echo "============================================="
|
||||||
|
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
|
echo "Did you update your docker image?"
|
||||||
|
echo "Run \`docker pull ubuntu\`"
|
||||||
|
echo "Run \`docker pull rust\`"
|
||||||
|
echo "Run \`docker build -t squoosh-rotate .\`"
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
11
codecs/rotate/package.json
Normal file
11
codecs/rotate/package.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "rotate",
|
||||||
|
"scripts": {
|
||||||
|
"build:image": "docker build -t squoosh-rotate .",
|
||||||
|
"build": "docker run --rm -v $(pwd):/src squoosh-rotate ./build.sh",
|
||||||
|
"benchmark": "echo File size after gzip && npm run benchmark:filesize && echo Optimizing && npm run -s benchmark:optimizing",
|
||||||
|
"benchmark:baseline": "v8 --liftoff --no-wasm-tier-up --no-opt ./benchmark.js",
|
||||||
|
"benchmark:optimizing": "v8 --no-liftoff --no-wasm-tier-up ./benchmark.js",
|
||||||
|
"benchmark:filesize": "cat rotate.wasm | gzip -c9n | wc -c"
|
||||||
|
}
|
||||||
|
}
|
||||||
113
codecs/rotate/rotate.rs
Normal file
113
codecs/rotate/rotate.rs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
use std::slice::{from_raw_parts, from_raw_parts_mut};
|
||||||
|
|
||||||
|
// This function is taken from Zachary Dremann
|
||||||
|
// https://github.com/GoogleChromeLabs/squoosh/pull/462
|
||||||
|
trait HardUnwrap<T> {
|
||||||
|
fn unwrap_hard(self) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> HardUnwrap<T> for Option<T> {
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
#[inline]
|
||||||
|
fn unwrap_hard(self) -> T {
|
||||||
|
match self {
|
||||||
|
Some(t) => t,
|
||||||
|
None => std::process::abort(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
fn unwrap_hard(self) -> T {
|
||||||
|
self.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const TILE_SIZE: usize = 16;
|
||||||
|
|
||||||
|
fn get_buffers<'a>(width: usize, height: usize) -> (&'a [u32], &'a mut [u32]) {
|
||||||
|
let num_pixels = width * height;
|
||||||
|
let in_b: &[u32];
|
||||||
|
let out_b: &mut [u32];
|
||||||
|
unsafe {
|
||||||
|
in_b = from_raw_parts::<u32>(8 as *const u32, num_pixels);
|
||||||
|
out_b = from_raw_parts_mut::<u32>((num_pixels * 4 + 8) as *mut u32, num_pixels);
|
||||||
|
}
|
||||||
|
return (in_b, out_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
fn rotate_0(width: usize, height: usize) {
|
||||||
|
let (in_b, out_b) = get_buffers(width, height);
|
||||||
|
for (in_p, out_p) in in_b.iter().zip(out_b.iter_mut()) {
|
||||||
|
*out_p = *in_p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
fn rotate_90(width: usize, height: usize) {
|
||||||
|
let (in_b, out_b) = get_buffers(width, height);
|
||||||
|
let new_width = height;
|
||||||
|
let _new_height = width;
|
||||||
|
for y_start in (0..height).step_by(TILE_SIZE) {
|
||||||
|
for x_start in (0..width).step_by(TILE_SIZE) {
|
||||||
|
for y in y_start..(y_start + TILE_SIZE).min(height) {
|
||||||
|
let in_offset = y * width;
|
||||||
|
let in_bounds = if x_start + TILE_SIZE < width {
|
||||||
|
(in_offset + x_start)..(in_offset + x_start + TILE_SIZE)
|
||||||
|
} else {
|
||||||
|
(in_offset + x_start)..(in_offset + width)
|
||||||
|
};
|
||||||
|
let in_chunk = in_b.get(in_bounds).unwrap_hard();
|
||||||
|
for (x, in_p) in in_chunk.iter().enumerate() {
|
||||||
|
let new_x = (new_width - 1) - y;
|
||||||
|
let new_y = x + x_start;
|
||||||
|
*out_b.get_mut(new_y * new_width + new_x).unwrap_hard() = *in_p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
fn rotate_180(width: usize, height: usize) {
|
||||||
|
let (in_b, out_b) = get_buffers(width, height);
|
||||||
|
for (in_p, out_p) in in_b.iter().zip(out_b.iter_mut().rev()) {
|
||||||
|
*out_p = *in_p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
fn rotate_270(width: usize, height: usize) {
|
||||||
|
let (in_b, out_b) = get_buffers(width, height);
|
||||||
|
let new_width = height;
|
||||||
|
let new_height = width;
|
||||||
|
for y_start in (0..height).step_by(TILE_SIZE) {
|
||||||
|
for x_start in (0..width).step_by(TILE_SIZE) {
|
||||||
|
for y in y_start..(y_start + TILE_SIZE).min(height) {
|
||||||
|
let in_offset = y * width;
|
||||||
|
let in_bounds = if x_start + TILE_SIZE < width {
|
||||||
|
(in_offset + x_start)..(in_offset + x_start + TILE_SIZE)
|
||||||
|
} else {
|
||||||
|
(in_offset + x_start)..(in_offset + width)
|
||||||
|
};
|
||||||
|
let in_chunk = in_b.get(in_bounds).unwrap_hard();
|
||||||
|
for (x, in_p) in in_chunk.iter().enumerate() {
|
||||||
|
let new_x = y;
|
||||||
|
let new_y = new_height - 1 - (x_start + x);
|
||||||
|
*out_b.get_mut(new_y * new_width + new_x).unwrap_hard() = *in_p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
fn rotate(width: usize, height: usize, rotate: usize) {
|
||||||
|
match rotate {
|
||||||
|
0 => rotate_0(width, height),
|
||||||
|
90 => rotate_90(width, height),
|
||||||
|
180 => rotate_180(width, height),
|
||||||
|
270 => rotate_270(width, height),
|
||||||
|
_ => std::process::abort(),
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
codecs/rotate/rotate.wasm
Executable file
BIN
codecs/rotate/rotate.wasm
Executable file
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
# WebP decoder
|
# WebP decoder
|
||||||
|
|
||||||
- Source: <https://github.com/webmproject/libwebp>
|
- Source: <https://github.com/webmproject/libwebp>
|
||||||
- Version: v0.6.1
|
- Version: v1.0.2
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
@@ -13,30 +13,10 @@ See `example.html`
|
|||||||
|
|
||||||
Returns the version of libwebp as a number. va.b.c is encoded as 0x0a0b0c
|
Returns the version of libwebp as a number. va.b.c is encoded as 0x0a0b0c
|
||||||
|
|
||||||
### `uint8_t* create_buffer(int size)`
|
### `RawImage decode(std::string buffer)`
|
||||||
|
|
||||||
Allocates an buffer for the file data.
|
Decodes the given webp buffer into raw RGBA. `RawImage` is a class with 3 fields: `buffer`, `width`, and `height`.
|
||||||
|
|
||||||
### `void destroy_buffer(uint8_t* p)`
|
|
||||||
|
|
||||||
Frees a buffer created with `create_buffer`.
|
|
||||||
|
|
||||||
### `void decode(uint8_t* img_in, int size)`
|
|
||||||
|
|
||||||
Decodes the given webp file into raw RGBA. The result is implicitly stored and can be accessed using the `get_result_*()` functions.
|
|
||||||
|
|
||||||
### `void free_result()`
|
### `void free_result()`
|
||||||
|
|
||||||
Frees the result created by `decode()`.
|
Frees the result created by `decode()`.
|
||||||
|
|
||||||
### `int get_result_pointer()`
|
|
||||||
|
|
||||||
Returns the pointer to the start of the buffer holding the encoded data. Length is width x height x 4 bytes.
|
|
||||||
|
|
||||||
### `int get_result_width()`
|
|
||||||
|
|
||||||
Returns the width of the image.
|
|
||||||
|
|
||||||
### `int get_result_height()`
|
|
||||||
|
|
||||||
Returns the height of the image.
|
|
||||||
|
|||||||
60
codecs/webp_dec/build.sh
Executable file
60
codecs/webp_dec/build.sh
Executable file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
export OPTIMIZE="-Os"
|
||||||
|
export LDFLAGS="${OPTIMIZE}"
|
||||||
|
export CFLAGS="${OPTIMIZE}"
|
||||||
|
export CPPFLAGS="${OPTIMIZE}"
|
||||||
|
apt-get update
|
||||||
|
apt-get install -qqy autoconf libtool libpng-dev pkg-config
|
||||||
|
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling libwebp"
|
||||||
|
echo "============================================="
|
||||||
|
test -n "$SKIP_LIBWEBP" || (
|
||||||
|
cd node_modules/libwebp
|
||||||
|
autoreconf -fiv
|
||||||
|
rm -rf build || true
|
||||||
|
mkdir -p build && cd build
|
||||||
|
emconfigure ../configure \
|
||||||
|
--disable-libwebpdemux \
|
||||||
|
--disable-wic \
|
||||||
|
--disable-gif \
|
||||||
|
--disable-tiff \
|
||||||
|
--disable-jpeg \
|
||||||
|
--disable-png \
|
||||||
|
--disable-sdl \
|
||||||
|
--disable-gl \
|
||||||
|
--disable-threading \
|
||||||
|
--disable-neon-rtcd \
|
||||||
|
--disable-neon \
|
||||||
|
--disable-sse2 \
|
||||||
|
--disable-sse4.1
|
||||||
|
emmake make
|
||||||
|
)
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling wasm bindings"
|
||||||
|
echo "============================================="
|
||||||
|
(
|
||||||
|
emcc \
|
||||||
|
${OPTIMIZE} \
|
||||||
|
--bind \
|
||||||
|
-s ALLOW_MEMORY_GROWTH=1 \
|
||||||
|
-s MODULARIZE=1 \
|
||||||
|
-s 'EXPORT_NAME="webp_dec"' \
|
||||||
|
--std=c++11 \
|
||||||
|
-I node_modules/libwebp \
|
||||||
|
-o ./webp_dec.js \
|
||||||
|
-x c++ \
|
||||||
|
webp_dec.cpp \
|
||||||
|
node_modules/libwebp/build/src/.libs/libwebp.a
|
||||||
|
)
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling wasm bindings done"
|
||||||
|
echo "============================================="
|
||||||
|
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
|
echo "Did you update your docker image?"
|
||||||
|
echo "Run \`docker pull trzeci/emscripten\`"
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
@@ -9,35 +9,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
Module.onRuntimeInitialized = async _ => {
|
Module.onRuntimeInitialized = async _ => {
|
||||||
const api = {
|
console.log('Version:', Module.version().toString(16));
|
||||||
version: Module.cwrap('version', 'number', []),
|
|
||||||
create_buffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
|
|
||||||
destroy_buffer: Module.cwrap('destroy_buffer', '', ['number']),
|
|
||||||
decode: Module.cwrap('decode', '', ['number', 'number']),
|
|
||||||
free_result: Module.cwrap('free_result', '', ['number']),
|
|
||||||
get_result_pointer: Module.cwrap('get_result_pointer', 'number', []),
|
|
||||||
get_result_width: Module.cwrap('get_result_width', 'number', []),
|
|
||||||
get_result_height: Module.cwrap('get_result_height', 'number', []),
|
|
||||||
};
|
|
||||||
console.log('Version:', api.version().toString(16));
|
|
||||||
const image = await loadFile('../example.webp');
|
const image = await loadFile('../example.webp');
|
||||||
const p = api.create_buffer(image.byteLength);
|
const result = Module.decode(image);
|
||||||
Module.HEAP8.set(new Uint8Array(image), p);
|
const imageData = new ImageData(new Uint8ClampedArray(result.buffer), result.width, result.height);
|
||||||
api.decode(p, image.byteLength);
|
Module.free_result();
|
||||||
const resultPointer = api.get_result_pointer();
|
|
||||||
if(resultPointer === 0) {
|
|
||||||
throw new Error("Could not decode image");
|
|
||||||
}
|
|
||||||
const resultWidth = api.get_result_width();
|
|
||||||
const resultHeight = api.get_result_height();
|
|
||||||
const resultView = new Uint8Array(Module.HEAP8.buffer, resultPointer, resultWidth * resultHeight * 4);
|
|
||||||
const result = new Uint8ClampedArray(resultView);
|
|
||||||
const imageData = new ImageData(result, resultWidth, resultHeight);
|
|
||||||
api.free_result(resultPointer);
|
|
||||||
api.destroy_buffer(p);
|
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = resultWidth;
|
canvas.width = result.width;
|
||||||
canvas.height = resultHeight;
|
canvas.height = result.height;
|
||||||
document.body.appendChild(canvas);
|
document.body.appendChild(canvas);
|
||||||
const ctx = canvas.getContext('2d');
|
const ctx = canvas.getContext('2d');
|
||||||
ctx.putImageData(imageData, 0, 0);
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
"name": "webp_dec",
|
"name": "webp_dec",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "napa",
|
"install": "napa",
|
||||||
"build": "docker run --rm -v $(pwd):/src trzeci/emscripten emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"cwrap\"]' -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s 'EXPORT_NAME=\"webp_dec\"' -I node_modules/libwebp -o ./webp_dec.js webp_dec.c node_modules/libwebp/src/{dec,dsp,demux,enc,mux,utils}/*.c"
|
"build": "docker run --rm -v $(pwd):/src trzeci/emscripten ./build.sh"
|
||||||
},
|
},
|
||||||
"napa": {
|
"napa": {
|
||||||
"libwebp": "webmproject/libwebp#v1.0.0"
|
"libwebp": "webmproject/libwebp#v1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"napa": "^3.0.0"
|
"napa": "3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
#include "emscripten.h"
|
|
||||||
#include "src/webp/decode.h"
|
|
||||||
#include "src/webp/demux.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
int version() {
|
|
||||||
return WebPGetDecoderVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
uint8_t* create_buffer(int size) {
|
|
||||||
return malloc(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
void destroy_buffer(uint8_t* p) {
|
|
||||||
free(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
int result[3];
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
void decode(uint8_t* img_in, int size) {
|
|
||||||
int width, height;
|
|
||||||
uint8_t* img_out = WebPDecodeRGBA(img_in, size, &width, &height);
|
|
||||||
result[0] = (int)img_out;
|
|
||||||
result[1] = width;
|
|
||||||
result[2] = height;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
void free_result() {
|
|
||||||
WebPFree(result[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
int get_result_pointer() {
|
|
||||||
return result[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
int get_result_width() {
|
|
||||||
return result[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
int get_result_height() {
|
|
||||||
return result[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
47
codecs/webp_dec/webp_dec.cpp
Normal file
47
codecs/webp_dec/webp_dec.cpp
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#include "emscripten/bind.h"
|
||||||
|
#include "emscripten/val.h"
|
||||||
|
#include "src/webp/decode.h"
|
||||||
|
#include "src/webp/demux.h"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using namespace emscripten;
|
||||||
|
|
||||||
|
int version() {
|
||||||
|
return WebPGetDecoderVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
class RawImage {
|
||||||
|
public:
|
||||||
|
val buffer;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
|
||||||
|
RawImage(val b, int w, int h)
|
||||||
|
: buffer(b), width(w), height(h) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t* last_result;
|
||||||
|
RawImage decode(std::string buffer) {
|
||||||
|
int width, height;
|
||||||
|
last_result = WebPDecodeRGBA((const uint8_t*)buffer.c_str(), buffer.size(), &width, &height);
|
||||||
|
return RawImage(
|
||||||
|
val(typed_memory_view(width*height*4, last_result)),
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_result() {
|
||||||
|
free(last_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
EMSCRIPTEN_BINDINGS(my_module) {
|
||||||
|
class_<RawImage>("RawImage")
|
||||||
|
.property("buffer", &RawImage::buffer)
|
||||||
|
.property("width", &RawImage::width)
|
||||||
|
.property("height", &RawImage::height);
|
||||||
|
|
||||||
|
function("decode", &decode);
|
||||||
|
function("version", &version);
|
||||||
|
function("free_result", &free_result);
|
||||||
|
}
|
||||||
13
codecs/webp_dec/webp_dec.d.ts
vendored
Normal file
13
codecs/webp_dec/webp_dec.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
interface RawImage {
|
||||||
|
buffer: Uint8Array;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WebPModule extends EmscriptenWasm.Module {
|
||||||
|
decode(data: BufferSource): RawImage;
|
||||||
|
free_result(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function(opts: EmscriptenWasm.ModuleOpts): WebPModule;
|
||||||
|
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -1,7 +1,7 @@
|
|||||||
# WebP encoder
|
# WebP encoder
|
||||||
|
|
||||||
- Source: <https://github.com/webmproject/libwebp>
|
- Source: <https://github.com/webmproject/libwebp>
|
||||||
- Version: v0.6.1
|
- Version: v1.0.2
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
@@ -17,26 +17,10 @@ See `example.html`
|
|||||||
|
|
||||||
Returns the version of libwebp as a number. va.b.c is encoded as 0x0a0b0c
|
Returns the version of libwebp as a number. va.b.c is encoded as 0x0a0b0c
|
||||||
|
|
||||||
### `uint8_t* create_buffer(int width, int height)`
|
### `UInt8Array encode(uint8_t* image_buffer, int image_width, int image_height, WebPConfig config)`
|
||||||
|
|
||||||
Allocates an RGBA buffer for an image with the given dimension.
|
Encodes the given image with given dimension to WebP.
|
||||||
|
|
||||||
### `void destroy_buffer(uint8_t* p)`
|
|
||||||
|
|
||||||
Frees a buffer created with `create_buffer`.
|
|
||||||
|
|
||||||
### `void encode(uint8_t* image_buffer, int image_width, int image_height, float quality)`
|
|
||||||
|
|
||||||
Encodes the given image with given dimension to WebP. `quality` is a number between 0 and 100. The higher the number, the better the quality of the encoded image. The result is implicitly stored and can be accessed using the `get_result_*()` functions.
|
|
||||||
|
|
||||||
### `void free_result()`
|
### `void free_result()`
|
||||||
|
|
||||||
Frees the result created by `encode()`.
|
Frees the last result created by `encode()`.
|
||||||
|
|
||||||
### `int get_result_pointer()`
|
|
||||||
|
|
||||||
Returns the pointer to the start of the buffer holding the encoded data.
|
|
||||||
|
|
||||||
### `int get_result_size()`
|
|
||||||
|
|
||||||
Returns the length of the buffer holding the encoded data.
|
|
||||||
|
|||||||
61
codecs/webp_enc/build.sh
Executable file
61
codecs/webp_enc/build.sh
Executable file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
export OPTIMIZE="-Os"
|
||||||
|
export LDFLAGS="${OPTIMIZE}"
|
||||||
|
export CFLAGS="${OPTIMIZE}"
|
||||||
|
export CPPFLAGS="${OPTIMIZE}"
|
||||||
|
|
||||||
|
apt-get update
|
||||||
|
apt-get install -qqy autoconf libtool libpng-dev pkg-config
|
||||||
|
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling libwebp"
|
||||||
|
echo "============================================="
|
||||||
|
test -n "$SKIP_LIBWEBP" || (
|
||||||
|
cd node_modules/libwebp
|
||||||
|
autoreconf -fiv
|
||||||
|
rm -rf build || true
|
||||||
|
mkdir -p build && cd build
|
||||||
|
emconfigure ../configure \
|
||||||
|
--disable-libwebpdemux \
|
||||||
|
--disable-wic \
|
||||||
|
--disable-gif \
|
||||||
|
--disable-tiff \
|
||||||
|
--disable-jpeg \
|
||||||
|
--disable-png \
|
||||||
|
--disable-sdl \
|
||||||
|
--disable-gl \
|
||||||
|
--disable-threading \
|
||||||
|
--disable-neon-rtcd \
|
||||||
|
--disable-neon \
|
||||||
|
--disable-sse2 \
|
||||||
|
--disable-sse4.1
|
||||||
|
emmake make
|
||||||
|
)
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling wasm bindings"
|
||||||
|
echo "============================================="
|
||||||
|
(
|
||||||
|
emcc \
|
||||||
|
${OPTIMIZE} \
|
||||||
|
--bind \
|
||||||
|
-s ALLOW_MEMORY_GROWTH=1 \
|
||||||
|
-s MODULARIZE=1 \
|
||||||
|
-s 'EXPORT_NAME="webp_enc"' \
|
||||||
|
--std=c++11 \
|
||||||
|
-I node_modules/libwebp \
|
||||||
|
-o ./webp_enc.js \
|
||||||
|
-x c++ \
|
||||||
|
webp_enc.cpp \
|
||||||
|
node_modules/libwebp/build/src/.libs/libwebp.a
|
||||||
|
)
|
||||||
|
echo "============================================="
|
||||||
|
echo "Compiling wasm bindings done"
|
||||||
|
echo "============================================="
|
||||||
|
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
|
echo "Did you update your docker image?"
|
||||||
|
echo "Run \`docker pull trzeci/emscripten\`"
|
||||||
|
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<script src='webp_enc.js'></script>
|
<script src='webp_enc.js'></script>
|
||||||
<script>
|
<script>
|
||||||
const Module = webp_enc();
|
const module = webp_enc();
|
||||||
|
|
||||||
async function loadImage(src) {
|
async function loadImage(src) {
|
||||||
// Load image
|
// Load image
|
||||||
@@ -17,29 +17,43 @@
|
|||||||
return ctx.getImageData(0, 0, img.width, img.height);
|
return ctx.getImageData(0, 0, img.width, img.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
Module.onRuntimeInitialized = async _ => {
|
module.onRuntimeInitialized = async _ => {
|
||||||
const api = {
|
console.log('Version:', module.version().toString(16));
|
||||||
version: Module.cwrap('version', 'number', []),
|
|
||||||
create_buffer: Module.cwrap('create_buffer', 'number', ['number', 'number']),
|
|
||||||
destroy_buffer: Module.cwrap('destroy_buffer', '', ['number']),
|
|
||||||
encode: Module.cwrap('encode', '', ['number', 'number', 'number', 'number']),
|
|
||||||
free_result: Module.cwrap('free_result', '', ['number']),
|
|
||||||
get_result_pointer: Module.cwrap('get_result_pointer', 'number', []),
|
|
||||||
get_result_size: Module.cwrap('get_result_size', 'number', []),
|
|
||||||
};
|
|
||||||
console.log('Version:', api.version().toString(16));
|
|
||||||
const image = await loadImage('../example.png');
|
const image = await loadImage('../example.png');
|
||||||
const p = api.create_buffer(image.width, image.height);
|
const result = module.encode(image.data, image.width, image.height, {
|
||||||
Module.HEAP8.set(image.data, p);
|
quality: 75,
|
||||||
api.encode(p, image.width, image.height, 2);
|
target_size: 0,
|
||||||
const resultPointer = api.get_result_pointer();
|
target_PSNR: 0,
|
||||||
const resultSize = api.get_result_size();
|
method: 4,
|
||||||
const resultView = new Uint8Array(Module.HEAP8.buffer, resultPointer, resultSize);
|
sns_strength: 50,
|
||||||
const result = new Uint8Array(resultView);
|
filter_strength: 60,
|
||||||
api.free_result(resultPointer);
|
filter_sharpness: 0,
|
||||||
api.destroy_buffer(p);
|
filter_type: 1,
|
||||||
|
partitions: 0,
|
||||||
|
segments: 4,
|
||||||
|
pass: 1,
|
||||||
|
show_compressed: 0,
|
||||||
|
preprocessing: 0,
|
||||||
|
autofilter: 0,
|
||||||
|
partition_limit: 0,
|
||||||
|
alpha_compression: 1,
|
||||||
|
alpha_filtering: 1,
|
||||||
|
alpha_quality: 100,
|
||||||
|
lossless: 0,
|
||||||
|
exact: 0,
|
||||||
|
image_hint: 0,
|
||||||
|
emulate_jpeg_size: 0,
|
||||||
|
thread_level: 0,
|
||||||
|
low_memory: 0,
|
||||||
|
near_lossless: 100,
|
||||||
|
use_delta_palette: 0,
|
||||||
|
use_sharp_yuv: 0,
|
||||||
|
});
|
||||||
|
console.log('size', result.length);
|
||||||
const blob = new Blob([result], {type: 'image/webp'});
|
const blob = new Blob([result], {type: 'image/webp'});
|
||||||
|
|
||||||
|
module.free_result();
|
||||||
|
|
||||||
const blobURL = URL.createObjectURL(blob);
|
const blobURL = URL.createObjectURL(blob);
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
img.src = blobURL;
|
img.src = blobURL;
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
"name": "webp_enc",
|
"name": "webp_enc",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "napa",
|
"install": "napa",
|
||||||
"build": "docker run --rm -v $(pwd):/src trzeci/emscripten emcc -O3 -s WASM=1 -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"cwrap\"]' -s ALLOW_MEMORY_GROWTH=1 -s MODULARIZE=1 -s 'EXPORT_NAME=\"webp_enc\"' -I node_modules/libwebp -o ./webp_enc.js webp_enc.c node_modules/libwebp/src/{dec,dsp,demux,enc,mux,utils}/*.c"
|
"build": "docker run --rm -v $(pwd):/src trzeci/emscripten ./build.sh"
|
||||||
},
|
},
|
||||||
"napa": {
|
"napa": {
|
||||||
"libwebp": "webmproject/libwebp#v1.0.0"
|
"libwebp": "webmproject/libwebp#v1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"napa": "^3.0.0"
|
"napa": "3.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
#include "emscripten.h"
|
|
||||||
#include "src/webp/encode.h"
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
int version() {
|
|
||||||
return WebPGetEncoderVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
uint8_t* create_buffer(int width, int height) {
|
|
||||||
return malloc(width * height * 4 * sizeof(uint8_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
void destroy_buffer(uint8_t* p) {
|
|
||||||
free(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
int result[2];
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
void encode(uint8_t* img_in, int width, int height, float quality) {
|
|
||||||
uint8_t* img_out;
|
|
||||||
size_t size;
|
|
||||||
size = WebPEncodeRGBA(img_in, width, height, width * 4, quality, &img_out);
|
|
||||||
result[0] = (int)img_out;
|
|
||||||
result[1] = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
void free_result() {
|
|
||||||
WebPFree(result[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
int get_result_pointer() {
|
|
||||||
return result[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
EMSCRIPTEN_KEEPALIVE
|
|
||||||
int get_result_size() {
|
|
||||||
return result[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
96
codecs/webp_enc/webp_enc.cpp
Normal file
96
codecs/webp_enc/webp_enc.cpp
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
#include <emscripten/bind.h>
|
||||||
|
#include <emscripten/val.h>
|
||||||
|
#include "src/webp/encode.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
using namespace emscripten;
|
||||||
|
|
||||||
|
int version() {
|
||||||
|
return WebPGetEncoderVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* last_result;
|
||||||
|
|
||||||
|
val encode(std::string img, int width, int height, WebPConfig config) {
|
||||||
|
uint8_t* img_in = (uint8_t*) img.c_str();
|
||||||
|
|
||||||
|
// A lot of this is duplicated from Encode in picture_enc.c
|
||||||
|
WebPPicture pic;
|
||||||
|
WebPMemoryWriter wrt;
|
||||||
|
int ok;
|
||||||
|
|
||||||
|
if (!WebPPictureInit(&pic)) {
|
||||||
|
// shouldn't happen, except if system installation is broken
|
||||||
|
throw std::runtime_error("Unexpected error");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only use use_argb if we really need it, as it's slower.
|
||||||
|
pic.use_argb = config.lossless || config.use_sharp_yuv || config.preprocessing > 0;
|
||||||
|
pic.width = width;
|
||||||
|
pic.height = height;
|
||||||
|
pic.writer = WebPMemoryWrite;
|
||||||
|
pic.custom_ptr = &wrt;
|
||||||
|
|
||||||
|
WebPMemoryWriterInit(&wrt);
|
||||||
|
|
||||||
|
ok = WebPPictureImportRGBA(&pic, (uint8_t*) img_in, width * 4) && WebPEncode(&config, &pic);
|
||||||
|
WebPPictureFree(&pic);
|
||||||
|
if (!ok) {
|
||||||
|
WebPMemoryWriterClear(&wrt);
|
||||||
|
throw std::runtime_error("Encode failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
last_result = wrt.mem;
|
||||||
|
|
||||||
|
return val(typed_memory_view(wrt.size, wrt.mem));
|
||||||
|
}
|
||||||
|
|
||||||
|
void free_result() {
|
||||||
|
WebPFree(last_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
EMSCRIPTEN_BINDINGS(my_module) {
|
||||||
|
enum_<WebPImageHint>("WebPImageHint")
|
||||||
|
.value("WEBP_HINT_DEFAULT", WebPImageHint::WEBP_HINT_DEFAULT)
|
||||||
|
.value("WEBP_HINT_PICTURE", WebPImageHint::WEBP_HINT_PICTURE)
|
||||||
|
.value("WEBP_HINT_PHOTO", WebPImageHint::WEBP_HINT_PHOTO)
|
||||||
|
.value("WEBP_HINT_GRAPH", WebPImageHint::WEBP_HINT_GRAPH)
|
||||||
|
;
|
||||||
|
|
||||||
|
value_object<WebPConfig>("WebPConfig")
|
||||||
|
.field("lossless", &WebPConfig::lossless)
|
||||||
|
.field("quality", &WebPConfig::quality)
|
||||||
|
.field("method", &WebPConfig::method)
|
||||||
|
.field("image_hint", &WebPConfig::image_hint)
|
||||||
|
.field("target_size", &WebPConfig::target_size)
|
||||||
|
.field("target_PSNR", &WebPConfig::target_PSNR)
|
||||||
|
.field("segments", &WebPConfig::segments)
|
||||||
|
.field("sns_strength", &WebPConfig::sns_strength)
|
||||||
|
.field("filter_strength", &WebPConfig::filter_strength)
|
||||||
|
.field("filter_sharpness", &WebPConfig::filter_sharpness)
|
||||||
|
.field("filter_type", &WebPConfig::filter_type)
|
||||||
|
.field("autofilter", &WebPConfig::autofilter)
|
||||||
|
.field("alpha_compression", &WebPConfig::alpha_compression)
|
||||||
|
.field("alpha_filtering", &WebPConfig::alpha_filtering)
|
||||||
|
.field("alpha_quality", &WebPConfig::alpha_quality)
|
||||||
|
.field("pass", &WebPConfig::pass)
|
||||||
|
.field("show_compressed", &WebPConfig::show_compressed)
|
||||||
|
.field("preprocessing", &WebPConfig::preprocessing)
|
||||||
|
.field("partitions", &WebPConfig::partitions)
|
||||||
|
.field("partition_limit", &WebPConfig::partition_limit)
|
||||||
|
.field("emulate_jpeg_size", &WebPConfig::emulate_jpeg_size)
|
||||||
|
.field("thread_level", &WebPConfig::thread_level)
|
||||||
|
.field("low_memory", &WebPConfig::low_memory)
|
||||||
|
.field("near_lossless", &WebPConfig::near_lossless)
|
||||||
|
.field("exact", &WebPConfig::exact)
|
||||||
|
.field("use_delta_palette", &WebPConfig::use_delta_palette)
|
||||||
|
.field("use_sharp_yuv", &WebPConfig::use_sharp_yuv)
|
||||||
|
;
|
||||||
|
|
||||||
|
function("version", &version);
|
||||||
|
function("encode", &encode);
|
||||||
|
function("free_result", &free_result);
|
||||||
|
}
|
||||||
9
codecs/webp_enc/webp_enc.d.ts
vendored
Normal file
9
codecs/webp_enc/webp_enc.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { EncodeOptions } from '../../src/codecs/webp/encoder-meta';
|
||||||
|
|
||||||
|
interface WebPModule extends EmscriptenWasm.Module {
|
||||||
|
encode(data: BufferSource, width: number, height: number, options: EncodeOptions): Uint8Array;
|
||||||
|
free_result(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default function(opts: EmscriptenWasm.ModuleOpts): WebPModule;
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
74
config/add-css-types.js
Normal file
74
config/add-css-types.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
const DtsCreator = require('typed-css-modules');
|
||||||
|
const chokidar = require('chokidar');
|
||||||
|
const util = require('util');
|
||||||
|
const sass = require('node-sass');
|
||||||
|
|
||||||
|
const sassRender = util.promisify(sass.render);
|
||||||
|
|
||||||
|
async function sassToCss(path) {
|
||||||
|
const result = await sassRender({ file: path });
|
||||||
|
return result.css;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} Opts
|
||||||
|
* @property {boolean} watch Watch for changes
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Create typing files for CSS & SCSS.
|
||||||
|
*
|
||||||
|
* @param {string[]} rootPaths Paths to search within
|
||||||
|
* @param {Opts} [opts={}] Options.
|
||||||
|
*/
|
||||||
|
function addCssTypes(rootPaths, opts = {}) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const { watch = false } = opts;
|
||||||
|
|
||||||
|
const paths = [];
|
||||||
|
const preReadyPromises = [];
|
||||||
|
let ready = false;
|
||||||
|
|
||||||
|
for (const rootPath of rootPaths) {
|
||||||
|
// Look for scss & css in each path.
|
||||||
|
paths.push(rootPath + '/**/*.scss');
|
||||||
|
paths.push(rootPath + '/**/*.css');
|
||||||
|
}
|
||||||
|
|
||||||
|
// For simplicity, the watcher is used even if we're not watching.
|
||||||
|
// If we're not watching, we stop the watcher after the initial files are found.
|
||||||
|
const watcher = chokidar.watch(paths, {
|
||||||
|
// Avoid processing already-processed files.
|
||||||
|
ignored: '*.d.*',
|
||||||
|
// Without this, travis and netlify builds never complete. I'm not sure why, but it might be
|
||||||
|
// related to https://github.com/paulmillr/chokidar/pull/758
|
||||||
|
persistent: watch,
|
||||||
|
});
|
||||||
|
|
||||||
|
function change(path) {
|
||||||
|
const promise = (async function() {
|
||||||
|
const creator = new DtsCreator({ camelCase: true });
|
||||||
|
const result = path.endsWith('.scss') ?
|
||||||
|
await creator.create(path, await sassToCss(path)) :
|
||||||
|
await creator.create(path);
|
||||||
|
|
||||||
|
await result.writeFile();
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (!ready) preReadyPromises.push(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
watcher.on('change', change);
|
||||||
|
watcher.on('add', change);
|
||||||
|
|
||||||
|
// 'ready' is when events have been fired for file discovery.
|
||||||
|
watcher.on('ready', () => {
|
||||||
|
ready = true;
|
||||||
|
// Wait for the current set of processing to finish.
|
||||||
|
Promise.all(preReadyPromises).then(resolve);
|
||||||
|
// And if we're not watching, close the watcher.
|
||||||
|
if (!watch) watcher.close();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = addCssTypes;
|
||||||
47
config/asset-template-plugin.js
Normal file
47
config/asset-template-plugin.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const ejs = require('ejs');
|
||||||
|
const AssetsPlugin = require('assets-webpack-plugin');
|
||||||
|
|
||||||
|
module.exports = class AssetTemplatePlugin extends AssetsPlugin {
|
||||||
|
constructor(options) {
|
||||||
|
options = options || {};
|
||||||
|
if (!options.template) throw Error('AssetTemplatePlugin: template option is required.');
|
||||||
|
super({
|
||||||
|
useCompilerPath: true,
|
||||||
|
filename: options.filename,
|
||||||
|
processOutput: files => this._processOutput(files)
|
||||||
|
});
|
||||||
|
this._template = path.resolve(process.cwd(), options.template);
|
||||||
|
const ignore = options.ignore || /(manifest\.json|\.DS_Store)$/;
|
||||||
|
this._ignore = typeof ignore === 'function' ? ({ test: ignore }) : ignore;
|
||||||
|
}
|
||||||
|
|
||||||
|
_processOutput(files) {
|
||||||
|
const mapping = {
|
||||||
|
all: [],
|
||||||
|
byType: {},
|
||||||
|
entries: {}
|
||||||
|
};
|
||||||
|
for (const entryName in files) {
|
||||||
|
// non-entry-point-derived assets are collected under an empty string key
|
||||||
|
// since that's a bit awkward, we'll call them "assets"
|
||||||
|
const name = entryName === '' ? 'assets' : entryName;
|
||||||
|
const listing = files[entryName];
|
||||||
|
const entry = mapping.entries[name] = {
|
||||||
|
all: [],
|
||||||
|
byType: {}
|
||||||
|
};
|
||||||
|
for (let type in listing) {
|
||||||
|
const list = [].concat(listing[type]).filter(file => !this._ignore.test(file));
|
||||||
|
if (!list.length) continue;
|
||||||
|
mapping.all = mapping.all.concat(list);
|
||||||
|
mapping.byType[type] = (mapping.byType[type] || []).concat(list);
|
||||||
|
entry.all = entry.all.concat(list);
|
||||||
|
entry.byType[type] = (entry.byType[type] || []).concat(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mapping.files = mapping.all;
|
||||||
|
return ejs.render(fs.readFileSync(this._template, 'utf8'), mapping);
|
||||||
|
}
|
||||||
|
};
|
||||||
158
config/auto-sw-plugin.js
Normal file
158
config/auto-sw-plugin.js
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
const util = require('util');
|
||||||
|
const minimatch = require('minimatch');
|
||||||
|
const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin');
|
||||||
|
const WebWorkerTemplatePlugin = require('webpack/lib/webworker/WebWorkerTemplatePlugin');
|
||||||
|
const ParserHelpers = require('webpack/lib/ParserHelpers');
|
||||||
|
|
||||||
|
const NAME = 'auto-sw-plugin';
|
||||||
|
const JS_TYPES = ['auto', 'esm', 'dynamic'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically finds and bundles Service Workers by looking for navigator.serviceWorker.register(..).
|
||||||
|
* An Array of webpack assets is injected into the Service Worker bundle as a `BUILD_ASSETS` global.
|
||||||
|
* Hidden and `.map` files are excluded by default, and this can be customized using the include & exclude options.
|
||||||
|
* @example
|
||||||
|
* // webpack config
|
||||||
|
* plugins: [
|
||||||
|
* new AutoSWPlugin({
|
||||||
|
* exclude: [
|
||||||
|
* '**\/.*', // don't expose hidden files (default)
|
||||||
|
* '**\/*.map', // don't precache sourcemaps (default)
|
||||||
|
* 'index.html' // don't cache the page itself
|
||||||
|
* ]
|
||||||
|
* })
|
||||||
|
* ]
|
||||||
|
* @param {Object} [options={}]
|
||||||
|
* @param {string[]} [options.exclude] Minimatch pattern(s) of which assets to omit from BUILD_ASSETS.
|
||||||
|
* @param {string[]} [options.include] Minimatch pattern(s) of assets to allow in BUILD_ASSETS.
|
||||||
|
*/
|
||||||
|
module.exports = class AutoSWPlugin {
|
||||||
|
constructor(options) {
|
||||||
|
this.options = Object.assign({
|
||||||
|
exclude: [
|
||||||
|
'**/*.map',
|
||||||
|
'**/.*'
|
||||||
|
]
|
||||||
|
}, options || {});
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(compiler) {
|
||||||
|
const serviceWorkers = [];
|
||||||
|
|
||||||
|
compiler.hooks.emit.tapPromise(NAME, compilation => this.emit(compiler, compilation, serviceWorkers));
|
||||||
|
|
||||||
|
compiler.hooks.normalModuleFactory.tap(NAME, (factory) => {
|
||||||
|
for (const type of JS_TYPES) {
|
||||||
|
factory.hooks.parser.for(`javascript/${type}`).tap(NAME, parser => {
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
const processRegisterCall = expr => {
|
||||||
|
const dep = parser.evaluateExpression(expr.arguments[0]);
|
||||||
|
|
||||||
|
if (!dep.isString()) {
|
||||||
|
parser.state.module.warnings.push({
|
||||||
|
message: 'navigator.serviceWorker.register() will only be bundled if passed a String literal.'
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filename = dep.string;
|
||||||
|
const outputFilename = this.options.filename || 'serviceworker.js'
|
||||||
|
const context = parser.state.current.context;
|
||||||
|
serviceWorkers.push({
|
||||||
|
outputFilename,
|
||||||
|
filename,
|
||||||
|
context
|
||||||
|
});
|
||||||
|
|
||||||
|
const id = `__webpack__serviceworker__${++counter}`;
|
||||||
|
ParserHelpers.toConstantDependency(parser, id)(expr.arguments[0]);
|
||||||
|
return ParserHelpers.addParsedVariableToModule(parser, id, '__webpack_public_path__ + ' + JSON.stringify(outputFilename));
|
||||||
|
};
|
||||||
|
|
||||||
|
parser.hooks.call.for('navigator.serviceWorker.register').tap(NAME, processRegisterCall);
|
||||||
|
parser.hooks.call.for('self.navigator.serviceWorker.register').tap(NAME, processRegisterCall);
|
||||||
|
parser.hooks.call.for('window.navigator.serviceWorker.register').tap(NAME, processRegisterCall);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createFilter(list) {
|
||||||
|
const filters = [].concat(list);
|
||||||
|
for (let i=0; i<filters.length; i++) {
|
||||||
|
if (typeof filters[i] === 'string') {
|
||||||
|
filters[i] = minimatch.filter(filters[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
async emit(compiler, compilation, serviceWorkers) {
|
||||||
|
let assetMapping = Object.keys(compilation.assets);
|
||||||
|
if (this.options.include) {
|
||||||
|
const filters = this.createFilter(this.options.include);
|
||||||
|
assetMapping = assetMapping.filter(filename => {
|
||||||
|
for (const filter of filters) {
|
||||||
|
if (filter(filename)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.options.exclude) {
|
||||||
|
const filters = this.createFilter(this.options.exclude);
|
||||||
|
assetMapping = assetMapping.filter(filename => {
|
||||||
|
for (const filter of filters) {
|
||||||
|
if (filter(filename)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await Promise.all(serviceWorkers.map(
|
||||||
|
(serviceWorker, index) => this.compileServiceWorker(compiler, compilation, serviceWorker, index, assetMapping)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
async compileServiceWorker(compiler, compilation, options, index, assetMapping) {
|
||||||
|
const entryFilename = options.filename;
|
||||||
|
|
||||||
|
const chunkFilename = compiler.options.output.chunkFilename.replace(/\.([a-z]+)$/i, '.serviceworker.$1');
|
||||||
|
const workerOptions = {
|
||||||
|
filename: options.outputFilename, // chunkFilename.replace(/\.?\[(?:chunkhash|contenthash|hash)(:\d+(?::\d+)?)?\]/g, ''),
|
||||||
|
chunkFilename: this.options.chunkFilename || chunkFilename,
|
||||||
|
globalObject: 'self'
|
||||||
|
};
|
||||||
|
|
||||||
|
const childCompiler = compilation.createChildCompiler(NAME, { filename: workerOptions.filename });
|
||||||
|
(new WebWorkerTemplatePlugin(workerOptions)).apply(childCompiler);
|
||||||
|
|
||||||
|
/* The duplication DefinePlugin ends up causing is problematic (it doesn't hoist injections), so we'll do it manually. */
|
||||||
|
// (new DefinePlugin({
|
||||||
|
// BUILD_ASSETS: JSON.stringify(assetMapping)
|
||||||
|
// })).apply(childCompiler);
|
||||||
|
(new SingleEntryPlugin(options.context, entryFilename, workerOptions.filename)).apply(childCompiler);
|
||||||
|
|
||||||
|
const subCache = `subcache ${__dirname} ${entryFilename} ${index}`;
|
||||||
|
let childCompilation;
|
||||||
|
childCompiler.hooks.compilation.tap(NAME, c => {
|
||||||
|
childCompilation = c;
|
||||||
|
if (childCompilation.cache) {
|
||||||
|
if (!childCompilation.cache[subCache]) childCompilation.cache[subCache] = {};
|
||||||
|
childCompilation.cache = childCompilation.cache[subCache];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await (util.promisify(childCompiler.runAsChild.bind(childCompiler)))();
|
||||||
|
|
||||||
|
const versionVar = this.options.version ?
|
||||||
|
`var VERSION = ${JSON.stringify(this.options.version)};` : '';
|
||||||
|
const original = childCompilation.assets[workerOptions.filename].source();
|
||||||
|
const source = `${versionVar}var BUILD_ASSETS=${JSON.stringify(assetMapping)};${original}`;
|
||||||
|
childCompilation.assets[workerOptions.filename] = {
|
||||||
|
source: () => source,
|
||||||
|
size: () => Buffer.byteLength(source, 'utf8')
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.assign(compilation.assets, childCompilation.assets);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
{
|
|
||||||
"hosting": {
|
|
||||||
"public": "build",
|
|
||||||
"ignore": [
|
|
||||||
"firebase.json",
|
|
||||||
"**/.*",
|
|
||||||
"**/node_modules/**"
|
|
||||||
],
|
|
||||||
"rewrites": [
|
|
||||||
{
|
|
||||||
"source": "**",
|
|
||||||
"destination": "/index.html"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"source": "**/*.@(eot|otf|ttf|ttc|woff|font.css)",
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"key": "Access-Control-Allow-Origin",
|
|
||||||
"value": "*"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "**/*.@(jpg|jpeg|gif|png|ico)",
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"key": "Cache-Control",
|
|
||||||
"value": "max-age=7200"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "**/*.@(js|css|json|manifest|map)",
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"key": "Cache-Control",
|
|
||||||
"value": "max-age=31536000"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "sw.js",
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"key": "Cache-Control",
|
|
||||||
"value": "max-age=0"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
global.d.ts
vendored
11
global.d.ts
vendored
@@ -1,22 +1,23 @@
|
|||||||
declare const __webpack_public_path__: string;
|
declare const __webpack_public_path__: string;
|
||||||
|
declare const PRERENDER: boolean;
|
||||||
|
|
||||||
declare interface NodeModule {
|
declare interface NodeModule {
|
||||||
hot: any;
|
hot: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare interface Window {
|
declare interface Window {
|
||||||
STATE: any
|
STATE: any;
|
||||||
|
ga: typeof ga;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare namespace JSX {
|
declare namespace JSX {
|
||||||
interface Element { }
|
interface Element { }
|
||||||
interface IntrinsicElements { }
|
interface IntrinsicElements { }
|
||||||
|
interface HTMLAttributes {
|
||||||
|
decoding?: string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'preact-i18n';
|
|
||||||
declare module 'preact-material-components-drawer';
|
|
||||||
declare module 'material-radial-progress';
|
|
||||||
|
|
||||||
declare module 'classnames' {
|
declare module 'classnames' {
|
||||||
export default function classnames(...args: any[]): string;
|
export default function classnames(...args: any[]): string;
|
||||||
}
|
}
|
||||||
|
|||||||
16540
package-lock.json
generated
Normal file
16540
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
120
package.json
120
package.json
@@ -1,15 +1,14 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"name": "squoosh",
|
"name": "squoosh",
|
||||||
"version": "0.0.0",
|
"version": "1.8.1",
|
||||||
"license": "apache-2.0",
|
"license": "apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:mozjpeg_enc": "cd codecs/mozjpeg_enc && npm run build",
|
"start": "webpack-dev-server --host 0.0.0.0 --hot",
|
||||||
"build:codecs": "npm run build:mozjpeg_enc",
|
|
||||||
"start": "webpack serve --host 0.0.0.0 --hot",
|
|
||||||
"build": "webpack -p",
|
"build": "webpack -p",
|
||||||
"lint": "tslint -c tslint.json -t verbose 'src/**/*.{ts,js}'",
|
"lint": "tslint -c tslint.json -p tsconfig.json -t verbose",
|
||||||
"lintfix": "tslint -c tslint.json -t verbose --fix 'src/**/*.{ts,js}'"
|
"lintfix": "tslint -c tslint.json -p tsconfig.json -t verbose --fix 'src/**/*.{ts,tsx,js,jsx}'",
|
||||||
|
"sizereport": "sizereport --config"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
@@ -17,58 +16,61 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^9.4.7",
|
"@types/node": "10.14.14",
|
||||||
"@types/webassembly-js-api": "0.0.1",
|
"@types/pretty-bytes": "5.1.0",
|
||||||
"babel-loader": "^7.1.4",
|
"@types/webassembly-js-api": "0.0.3",
|
||||||
"babel-plugin-jsx-pragmatic": "^1.0.2",
|
"@webcomponents/custom-elements": "1.2.4",
|
||||||
"babel-plugin-syntax-dynamic-import": "^6.18.0",
|
"@webpack-cli/serve": "0.1.8",
|
||||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
"assets-webpack-plugin": "3.9.10",
|
||||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
"chalk": "2.4.2",
|
||||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
"chokidar": "3.0.2",
|
||||||
"babel-plugin-transform-react-constant-elements": "^6.23.0",
|
"classnames": "2.2.6",
|
||||||
"babel-plugin-transform-react-jsx": "^6.24.1",
|
"clean-webpack-plugin": "1.0.1",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.13",
|
"comlink": "3.1.1",
|
||||||
"babel-preset-env": "^1.6.1",
|
"copy-webpack-plugin": "5.0.4",
|
||||||
"babel-register": "^6.26.0",
|
"critters-webpack-plugin": "2.4.0",
|
||||||
"clean-webpack-plugin": "^0.1.19",
|
"css-loader": "1.0.1",
|
||||||
"copy-webpack-plugin": "^4.5.1",
|
"ejs": "2.6.2",
|
||||||
"css-loader": "^0.28.11",
|
"escape-string-regexp": "2.0.0",
|
||||||
"exports-loader": "^0.7.0",
|
"exports-loader": "0.7.0",
|
||||||
"file-loader": "^1.1.11",
|
"file-drop-element": "0.2.0",
|
||||||
"html-webpack-plugin": "^3.0.6",
|
"file-loader": "4.1.0",
|
||||||
"husky": "^1.0.0-rc.9",
|
"gzip-size": "5.1.1",
|
||||||
"if-env": "^1.0.4",
|
"html-webpack-plugin": "3.2.0",
|
||||||
"loader-utils": "^1.1.0",
|
"husky": "3.0.2",
|
||||||
"mini-css-extract-plugin": "^0.3.0",
|
"idb-keyval": "3.2.0",
|
||||||
"node-sass": "^4.7.2",
|
"linkstate": "1.1.1",
|
||||||
"optimize-css-assets-webpack-plugin": "^4.0.0",
|
"loader-utils": "1.2.3",
|
||||||
"progress-bar-webpack-plugin": "^1.11.0",
|
"mini-css-extract-plugin": "0.8.0",
|
||||||
"raw-loader": "^0.5.1",
|
"minimatch": "3.0.4",
|
||||||
"sass-loader": "^6.0.7",
|
"node-fetch": "2.6.0",
|
||||||
"script-ext-html-webpack-plugin": "^2.0.1",
|
"node-sass": "4.12.0",
|
||||||
"source-map-loader": "^0.2.3",
|
"optimize-css-assets-webpack-plugin": "5.0.1",
|
||||||
"style-loader": "^0.20.3",
|
"pointer-tracker": "2.0.3",
|
||||||
"ts-loader": "^4.0.1",
|
"preact": "8.4.2",
|
||||||
"tslint": "^5.10.0",
|
"prerender-loader": "1.3.0",
|
||||||
"tslint-config-airbnb": "^5.9.2",
|
"pretty-bytes": "5.3.0",
|
||||||
"tslint-config-semistandard": "^7.0.0",
|
"progress-bar-webpack-plugin": "1.12.1",
|
||||||
"tslint-react": "^3.5.1",
|
"raw-loader": "3.1.0",
|
||||||
"typescript": "^2.7.2",
|
"readdirp": "3.1.1",
|
||||||
"typings-for-css-modules-loader": "^1.7.0",
|
"sass-loader": "7.1.0",
|
||||||
"webpack": "^4.3.0",
|
"script-ext-html-webpack-plugin": "2.1.4",
|
||||||
"webpack-bundle-analyzer": "^2.11.1",
|
"source-map-loader": "0.2.4",
|
||||||
"webpack-cli": "^2.0.13",
|
"style-loader": "0.23.1",
|
||||||
"webpack-dev-server": "^3.1.1",
|
"terser-webpack-plugin": "1.4.1",
|
||||||
"webpack-plugin-replace": "^1.1.1"
|
"travis-size-report": "1.1.0",
|
||||||
},
|
"ts-loader": "6.0.3",
|
||||||
"dependencies": {
|
"tslint": "5.18.0",
|
||||||
"classnames": "^2.2.5",
|
"tslint-config-airbnb": "5.11.1",
|
||||||
"comlink": "^3.0.3",
|
"tslint-config-semistandard": "8.0.1",
|
||||||
"comlink-loader": "^1.0.0",
|
"tslint-react": "4.0.0",
|
||||||
"material-components-web": "^0.32.0",
|
"typed-css-modules": "0.4.2",
|
||||||
"preact": "^8.2.7",
|
"typescript": "3.5.3",
|
||||||
"preact-i18n": "^1.2.0",
|
"url-loader": "2.1.0",
|
||||||
"preact-material-components": "^1.3.7",
|
"webpack": "4.28.0",
|
||||||
"preact-router": "^2.6.0"
|
"webpack-bundle-analyzer": "3.4.1",
|
||||||
|
"webpack-cli": "3.3.4",
|
||||||
|
"webpack-dev-server": "3.7.2",
|
||||||
|
"worker-plugin": "3.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user