Compare commits
119 Commits
visdif-err
...
basis
Author | SHA1 | Date | |
---|---|---|---|
3ce7bcea5f | |||
a547491146 | |||
6e427f9208 | |||
0d35fbd349 | |||
4e901c714c | |||
b74788e036 | |||
14c3d190e9 | |||
eeaa19589e | |||
35b8c56f1a | |||
816d1f92fd | |||
4091f2efec | |||
821d14c6ab | |||
a72ca46531 | |||
fafcf97f0c | |||
d526877147 | |||
0ed7ef842f | |||
de4eb9c8f7 | |||
16a53caa48 | |||
3d4c62fede | |||
ce7be359c0 | |||
3f2dd66726 | |||
9198f748b8 | |||
a726adf0e8 | |||
04580b0bcb | |||
a040c47047 | |||
2427763a14 | |||
c582c54922 | |||
9ae27c1887 | |||
bf95eb39c2 | |||
011c0346c1 | |||
9b36c3f9af | |||
490fe2aace | |||
48efb4ddeb | |||
0977bd94d3 | |||
d5f12a8c61 | |||
fb867dcdaa | |||
f038c1bd7d | |||
b37cc0784f | |||
ff40000473 | |||
7232524f4d | |||
bf683cdf59 | |||
f848a9384e | |||
d4d0db6c49 | |||
0719abff27 | |||
2c561687af | |||
cd20082e5d | |||
8262c79bb6 | |||
ef176d7565 | |||
3b0b7dbdb1 | |||
cc9a887386 | |||
71ca1ab0ca | |||
f5d9535fd2 | |||
cea6a61366 | |||
192cfc62ee | |||
fe21322b2b | |||
d953822d19 | |||
021b082884 | |||
ad5002c79c | |||
883bb92e48 | |||
79efd0d32e | |||
99741d665d | |||
b38765b4e6 | |||
a11ac15008 | |||
4f6d21199c | |||
fb7e00067f | |||
a2121ec47b | |||
d4056026fb | |||
d12b040bd3 | |||
149ebf5a67 | |||
d8297aad10 | |||
0e84a5b5f7 | |||
187a5bed01 | |||
07a288398e | |||
dbb31a1add | |||
6e58e8edb0 | |||
955079b18f | |||
f6f70c590e | |||
55c4aa51ac | |||
aea316c604 | |||
d604e94ad2 | |||
61de471e52 | |||
955b2ac1ba | |||
3d225966c5 | |||
851da25302 | |||
32e3528666 | |||
1e52837931 | |||
1ec3b36858 | |||
0cb454295b | |||
2d4c1906e1 | |||
c036866478 | |||
8dc532eb09 | |||
41f4f7778f | |||
9278b8a1ab | |||
dc86b33634 | |||
e4832644f2 | |||
1dc24a5c36 | |||
8eb29bd792 | |||
83db97856c | |||
925e549466 | |||
e0895ca074 | |||
a6f3bb596a | |||
c9bc01b111 | |||
c5bbce6a1d | |||
e8b1db5da6 | |||
be41088fb8 | |||
558ee0e5ba | |||
114d6869ea | |||
c9b83a8716 | |||
bdbb8b81ac | |||
ba5640835f | |||
88106a09f5 | |||
52ca417d3a | |||
1527b1431c | |||
189d196b2b | |||
cf66f2a69d | |||
d3c1877e76 | |||
bb036df1fc | |||
2e3361af79 | |||
4dd2296eaf |
44
README.md
44
README.md
@ -1,36 +1,42 @@
|
||||
# [Squoosh]!
|
||||
|
||||
[Squoosh] is an image compression web app that allows you to dive into the advanced options provided
|
||||
by various image compressors.
|
||||
[Squoosh] is an image compression web app that reduces image sizes through numerous formats.
|
||||
|
||||
# API & CLI
|
||||
|
||||
Squoosh now has [an API](https://github.com/GoogleChromeLabs/squoosh/tree/dev/libsquoosh) and [a CLI](https://github.com/GoogleChromeLabs/squoosh/tree/dev/cli) that allows you to compress many images at once.
|
||||
Squoosh has [an API](https://github.com/GoogleChromeLabs/squoosh/tree/dev/libsquoosh) and [a CLI](https://github.com/GoogleChromeLabs/squoosh/tree/dev/cli) to compress many images at once.
|
||||
|
||||
# Privacy
|
||||
|
||||
Google Analytics is used to record the following:
|
||||
Squoosh does not send your image to a server. All image compression processes locally.
|
||||
|
||||
- [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.
|
||||
- If install is available, when Squoosh is installed, and what method was used to install Squoosh.
|
||||
However, Squoosh utilizes Google Analytics to collect the following:
|
||||
|
||||
Image compression is handled locally; no additional data is sent to the server.
|
||||
- [Basic visitor data](https://support.google.com/analytics/answer/6004245?ref_topic=2919631).
|
||||
- The before and after image size value.
|
||||
- If Squoosh PWA, the type of Squoosh installation.
|
||||
- If Squoosh PWA, the installation time and date.
|
||||
|
||||
# Building locally
|
||||
# Developing
|
||||
|
||||
Clone the repo, and:
|
||||
To develop for Squoosh:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
1. Clone the repository
|
||||
1. To install node packages, run:
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
1. Then build the app by running:
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
1. After building, start the development server by running:
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
You can run the development server with:
|
||||
# Contributing
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
Squoosh is an open-source project that appreciates all community involvement. To contribute to the project, follow the [contribute guide](/CONTRIBUTING.md).
|
||||
|
||||
[squoosh]: https://squoosh.app
|
||||
|
@ -42,7 +42,7 @@ Options:
|
||||
-h, --help display help for command
|
||||
```
|
||||
|
||||
The default values for each `config` option can be found in the [`codecs.js`][codecs.js] file under `defaultEncoderOptions`. Every unspecified value will use the default value specified here. _Better documentation is needed here._
|
||||
The default values for each `config` option can be found in the [`codecs.ts`][codecs.ts] file under `defaultEncoderOptions`. Every unspecified value will use the default value specified here. _Better documentation is needed here._
|
||||
|
||||
## Auto optimizer
|
||||
|
||||
@ -55,5 +55,5 @@ $ npx @squoosh/cli --wp2 auto test.png
|
||||
```
|
||||
|
||||
[squoosh]: https://squoosh.app
|
||||
[codecs.js]: https://github.com/GoogleChromeLabs/squoosh/blob/dev/libsquoosh/src/codecs.js
|
||||
[codecs.ts]: https://github.com/GoogleChromeLabs/squoosh/blob/dev/libsquoosh/src/codecs.ts
|
||||
[butteraugli]: https://github.com/google/butteraugli
|
||||
|
36
cli/package-lock.json
generated
36
cli/package-lock.json
generated
@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@squoosh/cli",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@squoosh/cli",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.2",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@squoosh/lib": "^0.2.0",
|
||||
"@squoosh/lib": "^0.4.0",
|
||||
"commander": "^7.2.0",
|
||||
"json5": "^2.2.0",
|
||||
"kleur": "^4.1.4",
|
||||
@ -18,14 +18,21 @@
|
||||
"bin": {
|
||||
"cli": "src/index.js",
|
||||
"squoosh-cli": "src/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": " ^12.20.2 || ^14.13.1 || ^16.0.0 "
|
||||
}
|
||||
},
|
||||
"node_modules/@squoosh/lib": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@squoosh/lib/-/lib-0.2.0.tgz",
|
||||
"integrity": "sha512-zKId9h/LzEnCdoOGnIgjzvqbk5g2aHgfEqgKQ+S+r5K3TasgD/DAsT6r7v6gXft1ao0f/00CTcwIp1KviWTQbw==",
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@squoosh/lib/-/lib-0.4.0.tgz",
|
||||
"integrity": "sha512-O1LyugWLZjMI4JZeZMA5vzfhfPjfMZXH5/HmVkRagP8B70wH3uoR7tjxfGNdSavey357MwL8YJDxbGwBBdHp7Q==",
|
||||
"dependencies": {
|
||||
"wasm-feature-detect": "^1.2.11",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": " ^12.5.0 || ^14.0.0 || ^16.0.0 "
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
@ -323,6 +330,11 @@
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"node_modules/wasm-feature-detect": {
|
||||
"version": "1.2.11",
|
||||
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
|
||||
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w=="
|
||||
},
|
||||
"node_modules/wcwidth": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
|
||||
@ -342,10 +354,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@squoosh/lib": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@squoosh/lib/-/lib-0.2.0.tgz",
|
||||
"integrity": "sha512-zKId9h/LzEnCdoOGnIgjzvqbk5g2aHgfEqgKQ+S+r5K3TasgD/DAsT6r7v6gXft1ao0f/00CTcwIp1KviWTQbw==",
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@squoosh/lib/-/lib-0.4.0.tgz",
|
||||
"integrity": "sha512-O1LyugWLZjMI4JZeZMA5vzfhfPjfMZXH5/HmVkRagP8B70wH3uoR7tjxfGNdSavey357MwL8YJDxbGwBBdHp7Q==",
|
||||
"requires": {
|
||||
"wasm-feature-detect": "^1.2.11",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
}
|
||||
},
|
||||
@ -578,6 +591,11 @@
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"wasm-feature-detect": {
|
||||
"version": "1.2.11",
|
||||
"resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.2.11.tgz",
|
||||
"integrity": "sha512-HUqwaodrQGaZgz1lZaNioIkog9tkeEJjrM3eq4aUL04whXOVDRc/o2EGb/8kV0QX411iAYWEqq7fMBmJ6dKS6w=="
|
||||
},
|
||||
"wcwidth": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
|
||||
|
@ -1,9 +1,14 @@
|
||||
{
|
||||
"name": "@squoosh/cli",
|
||||
"version": "0.7.0",
|
||||
"version": "0.7.2",
|
||||
"description": "A CLI for Squoosh",
|
||||
"public": true,
|
||||
"type": "module",
|
||||
"homepage": "https://github.com/GoogleChromeLabs/squoosh",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GoogleChromeLabs/squoosh.git"
|
||||
},
|
||||
"bin": {
|
||||
"squoosh-cli": "src/index.js",
|
||||
"@squoosh/cli": "src/index.js"
|
||||
@ -14,8 +19,11 @@
|
||||
"keywords": [],
|
||||
"author": "Google Chrome Developers <chromium-dev@google.com>",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": " ^12.20.2 || ^14.13.1 || ^16.0.0 "
|
||||
},
|
||||
"dependencies": {
|
||||
"@squoosh/lib": "^0.2.0",
|
||||
"@squoosh/lib": "^0.4.0",
|
||||
"commander": "^7.2.0",
|
||||
"json5": "^2.2.0",
|
||||
"kleur": "^4.1.4",
|
||||
|
@ -75,7 +75,9 @@ async function getInputFiles(paths) {
|
||||
|
||||
for (const inputPath of paths) {
|
||||
const files = (await fsp.lstat(inputPath)).isDirectory()
|
||||
? (await fsp.readdir(inputPath, {withFileTypes: true})).filter(dirent => dirent.isFile()).map(dirent => path.join(inputPath, dirent.name))
|
||||
? (await fsp.readdir(inputPath, { withFileTypes: true }))
|
||||
.filter((dirent) => dirent.isFile())
|
||||
.map((dirent) => path.join(inputPath, dirent.name))
|
||||
: [inputPath];
|
||||
for (const file of files) {
|
||||
try {
|
||||
@ -177,8 +179,8 @@ async function processFiles(files) {
|
||||
jobsFinished++;
|
||||
const outputPath = path.join(
|
||||
program.opts().outputDir,
|
||||
program.opts().suffix +
|
||||
path.basename(originalFile, path.extname(originalFile)),
|
||||
path.basename(originalFile, path.extname(originalFile)) +
|
||||
program.opts().suffix
|
||||
);
|
||||
for (const output of Object.values(image.encodedWith)) {
|
||||
const outputFile = `${outputPath}.${(await output).extension}`;
|
||||
|
@ -16,10 +16,10 @@ export
|
||||
OUT_ENC_JS = enc/avif_enc.js
|
||||
OUT_NODE_ENC_JS = enc/avif_node_enc.js
|
||||
OUT_ENC_MT_JS = enc/avif_enc_mt.js
|
||||
OUT_NODE_ENC_MT_JS = enc/avif_node_enc_mt.js
|
||||
OUT_DEC_JS = dec/avif_dec.js
|
||||
OUT_NODE_DEC_JS = dec/avif_node_dec.js
|
||||
|
||||
OUT_ENC_CPP = enc/avif_enc.cpp
|
||||
OUT_ENC_CPP = enc/avif_enc.cpp
|
||||
OUT_DEC_CPP = dec/avif_dec.cpp
|
||||
ENVIRONMENT = worker
|
||||
@ -28,9 +28,9 @@ HELPER_MAKEFLAGS := -f helper.Makefile
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(OUT_ENC_JS) $(OUT_DEC_JS) $(OUT_ENC_MT_JS) $(OUT_NODE_ENC_JS) $(OUT_NODE_DEC_JS)
|
||||
all: $(OUT_ENC_JS) $(OUT_DEC_JS) $(OUT_ENC_MT_JS) $(OUT_NODE_ENC_JS) $(OUT_NODE_ENC_MT_JS) $(OUT_NODE_DEC_JS)
|
||||
|
||||
$(OUT_NODE_ENC_JS): ENVIRONMENT=node
|
||||
$(OUT_NODE_ENC_JS) $(OUT_NODE_ENC_MT_JS): ENVIRONMENT=node
|
||||
$(OUT_NODE_ENC_JS) $(OUT_ENC_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
||||
$(MAKE) \
|
||||
$(HELPER_MAKEFLAGS) \
|
||||
@ -44,7 +44,7 @@ $(OUT_NODE_ENC_JS) $(OUT_ENC_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(L
|
||||
ENVIRONMENT=$(ENVIRONMENT) \
|
||||
LIBAVIF_FLAGS="-DAVIF_CODEC_AOM_DECODE=0"
|
||||
|
||||
$(OUT_ENC_MT_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
||||
$(OUT_ENC_MT_JS) $(OUT_NODE_ENC_MT_JS): $(OUT_ENC_CPP) $(CODEC_DIR)/CMakeLists.txt $(LIBAOM_DIR)/CMakeLists.txt
|
||||
$(MAKE) \
|
||||
$(HELPER_MAKEFLAGS) \
|
||||
OUT_JS=$@ \
|
||||
@ -89,4 +89,7 @@ $(LIBAOM_DIR)/CMakeLists.txt: $(LIBAOM_PACKAGE)
|
||||
clean:
|
||||
$(MAKE) $(HELPER_MAKEFLAGS) OUT_JS=$(OUT_ENC_JS) clean
|
||||
$(MAKE) $(HELPER_MAKEFLAGS) OUT_JS=$(OUT_ENC_MT_JS) clean
|
||||
$(MAKE) $(HELPER_MAKEFLAGS) OUT_JS=$(OUT_ENC_NODE_JS) clean
|
||||
$(MAKE) $(HELPER_MAKEFLAGS) OUT_JS=$(OUT_ENC_NODE_MT_JS) clean
|
||||
$(MAKE) $(HELPER_MAKEFLAGS) OUT_JS=$(OUT_DEC_JS) clean
|
||||
$(MAKE) $(HELPER_MAKEFLAGS) OUT_JS=$(OUT_DEV_NODE_JS) clean
|
||||
|
Binary file not shown.
Binary file not shown.
2
codecs/avif/enc/avif_enc.js
generated
2
codecs/avif/enc/avif_enc.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/avif/enc/avif_enc_mt.js
generated
2
codecs/avif/enc/avif_enc_mt.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/avif/enc/avif_node_enc.js
generated
2
codecs/avif/enc/avif_node_enc.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
16
codecs/avif/enc/avif_node_enc_mt.js
generated
Normal file
16
codecs/avif/enc/avif_node_enc_mt.js
generated
Normal file
File diff suppressed because one or more lines are too long
BIN
codecs/avif/enc/avif_node_enc_mt.wasm
Executable file
BIN
codecs/avif/enc/avif_node_enc_mt.wasm
Executable file
Binary file not shown.
1
codecs/avif/enc/avif_node_enc_mt.worker.js
generated
Normal file
1
codecs/avif/enc/avif_node_enc_mt.worker.js
generated
Normal file
@ -0,0 +1 @@
|
||||
"use strict";var Module={};if(typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string"){var nodeWorkerThreads=require("worker_threads");var parentPort=nodeWorkerThreads.parentPort;parentPort.on("message",function(data){onmessage({data:data})});var nodeFS=require("fs");Object.assign(global,{self:global,require:require,Module:Module,location:{href:__filename},Worker:nodeWorkerThreads.Worker,importScripts:function(f){(0,eval)(nodeFS.readFileSync(f,"utf8"))},postMessage:function(msg){parentPort.postMessage(msg)},performance:global.performance||{now:function(){return Date.now()}}})}var initializedJS=false;function threadPrintErr(){var text=Array.prototype.slice.call(arguments).join(" ");console.error(text)}function threadAlert(){var text=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:text,threadId:Module["_pthread_self"]()})}var err=threadPrintErr;self.alert=threadAlert;Module["instantiateWasm"]=function(info,receiveInstance){var instance=new WebAssembly.Instance(Module["wasmModule"],info);receiveInstance(instance);Module["wasmModule"]=null;return instance.exports};function moduleLoaded(){}self.onmessage=function(e){try{if(e.data.cmd==="load"){Module["wasmModule"]=e.data.wasmModule;Module["wasmMemory"]=e.data.wasmMemory;Module["buffer"]=Module["wasmMemory"].buffer;Module["ENVIRONMENT_IS_PTHREAD"]=true;(e.data.urlOrBlob?import(e.data.urlOrBlob):import("./avif_node_enc_mt.js")).then(function(exports){return exports.default(Module)}).then(function(instance){Module=instance;moduleLoaded()})}else if(e.data.cmd==="objectTransfer"){Module["PThread"].receiveObjectTransfer(e.data)}else if(e.data.cmd==="run"){Module["__performance_now_clock_drift"]=performance.now()-e.data.time;Module["__emscripten_thread_init"](e.data.threadInfoStruct,0,0);var max=e.data.stackBase;var top=e.data.stackBase+e.data.stackSize;Module["establishStackSpace"](top,max);Module["PThread"].receiveObjectTransfer(e.data);Module["PThread"].threadInit();if(!initializedJS){Module["___embind_register_native_and_builtin_types"]();initializedJS=true}try{var result=Module["invokeEntryPoint"](e.data.start_routine,e.data.arg);if(Module["keepRuntimeAlive"]()){Module["PThread"].setExitStatus(result)}else{Module["PThread"].threadExit(result)}}catch(ex){if(ex==="Canceled!"){Module["PThread"].threadCancel()}else if(ex!="unwind"){if(ex instanceof Module["ExitStatus"]){if(Module["keepRuntimeAlive"]()){}else{Module["PThread"].threadExit(ex.status)}}else{Module["PThread"].threadExit(-2);throw ex}}}}else if(e.data.cmd==="cancel"){if(Module["_pthread_self"]()){Module["PThread"].threadCancel()}}else if(e.data.target==="setimmediate"){}else if(e.data.cmd==="processThreadQueue"){if(Module["_pthread_self"]()){Module["_emscripten_current_thread_process_queued_calls"]()}}else{err("worker.js received unknown command "+e.data.cmd);err(e.data)}}catch(ex){err("worker.js onmessage() captured an uncaught exception: "+ex);if(ex&&ex.stack)err(ex.stack);throw ex}};
|
201
codecs/basis/LICENSE.codec.md
Normal file
201
codecs/basis/LICENSE.codec.md
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
83
codecs/basis/Makefile
Normal file
83
codecs/basis/Makefile
Normal file
@ -0,0 +1,83 @@
|
||||
CODEC_URL := https://github.com/BinomialLLC/basis_universal/archive/refs/tags/v1.15_rel2.tar.gz
|
||||
CODEC_DIR := node_modules/basis
|
||||
CODEC_BUILD_DIR := $(CODEC_DIR)/build
|
||||
CODEC_LIB := $(CODEC_BUILD_DIR)/basis.a
|
||||
ENVIRONMENT = worker
|
||||
|
||||
OUT_JS := enc/basis_enc.js dec/basis_dec.js
|
||||
OUT_WASM := $(OUT_JS:.js=.wasm)
|
||||
|
||||
COMMON_FLAGS := -O3 -fno-strict-aliasing
|
||||
|
||||
override CXXFLAGS += $(COMMON_FLAGS)
|
||||
override CFLAGS += $(COMMAN_FLAGS)
|
||||
|
||||
CODEC_CPP_SOURCE_FILES := \
|
||||
encoder/basisu_comp.cpp \
|
||||
encoder/basisu_enc.cpp \
|
||||
encoder/basisu_backend.cpp \
|
||||
encoder/basisu_basis_file.cpp \
|
||||
encoder/basisu_etc.cpp \
|
||||
encoder/basisu_uastc_enc.cpp \
|
||||
encoder/basisu_gpu_texture.cpp \
|
||||
encoder/basisu_frontend.cpp \
|
||||
encoder/basisu_bc7enc.cpp \
|
||||
encoder/basisu_pvrtc1_4.cpp \
|
||||
encoder/basisu_astc_decomp.cpp \
|
||||
encoder/basisu_global_selector_palette_helpers.cpp \
|
||||
encoder/basisu_resampler.cpp \
|
||||
encoder/basisu_kernels_sse.cpp \
|
||||
encoder/basisu_resample_filters.cpp \
|
||||
encoder/jpgd.cpp \
|
||||
encoder/lodepng.cpp \
|
||||
transcoder/basisu_transcoder.cpp
|
||||
|
||||
CODEC_C_SOURCE_FILES := \
|
||||
encoder/apg_bmp.c \
|
||||
zstd/zstd.c
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(CODEC_DIR) $(OUT_JS)
|
||||
|
||||
# Define dependencies for all variations of build artifacts.
|
||||
$(filter enc/%,$(OUT_JS)): enc/basis_enc.cpp
|
||||
$(filter dec/%,$(OUT_JS)): dec/basis_dec.cpp
|
||||
|
||||
# TODO: Make it build for node
|
||||
# enc/mozjpeg_node_enc.js dec/mozjpeg_node_dec.js: ENVIRONMENT = node
|
||||
|
||||
%.js: $(CODEC_LIB)
|
||||
$(CXX) \
|
||||
-I $(CODEC_DIR)/encoder \
|
||||
-I $(CODEC_DIR)/transcoder \
|
||||
${CXXFLAGS} \
|
||||
${LDFLAGS} \
|
||||
--closure 1 \
|
||||
--bind \
|
||||
-s ALLOW_MEMORY_GROWTH=1 \
|
||||
-s MODULARIZE=1 \
|
||||
-s TEXTDECODER=2 \
|
||||
-s ENVIRONMENT=$(ENVIRONMENT) \
|
||||
-s EXPORT_ES6=1 \
|
||||
-o $@ \
|
||||
$+
|
||||
|
||||
$(CODEC_LIB): $(CODEC_DIR)
|
||||
mkdir -p $(CODEC_BUILD_DIR)
|
||||
cd $(CODEC_BUILD_DIR) && \
|
||||
$(CXX) \
|
||||
${CXXFLAGS} \
|
||||
-c $(addprefix ../, $(CODEC_CPP_SOURCE_FILES))
|
||||
cd $(CODEC_BUILD_DIR) && \
|
||||
$(CC) \
|
||||
${CFLAGS} \
|
||||
-c $(addprefix ../, $(CODEC_C_SOURCE_FILES))
|
||||
$(AR) rc $(CODEC_LIB) $(CODEC_BUILD_DIR)/*.o
|
||||
|
||||
$(CODEC_DIR):
|
||||
mkdir -p $@
|
||||
curl -sL $(CODEC_URL) | tar xz --strip 1 -C $@
|
||||
|
||||
clean:
|
||||
$(RM) -r $(OUT_JS) $(OUT_WASM) $(CODEC_BUILD_DIR)
|
46
codecs/basis/dec/basis_dec.cpp
Normal file
46
codecs/basis/dec/basis_dec.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
#include <inttypes.h>
|
||||
#include <string>
|
||||
#include "basisu_global_selector_palette.h"
|
||||
#include "basisu_transcoder.h"
|
||||
|
||||
using namespace emscripten;
|
||||
using namespace basisu;
|
||||
|
||||
thread_local const val Uint8ClampedArray = val::global("Uint8ClampedArray");
|
||||
thread_local const val ImageData = val::global("ImageData");
|
||||
|
||||
val decode(std::string data) {
|
||||
basist::basisu_transcoder_init();
|
||||
|
||||
basist::etc1_global_selector_codebook sel_codebook = basist::etc1_global_selector_codebook(
|
||||
basist::g_global_selector_cb_size, basist::g_global_selector_cb);
|
||||
basist::ktx2_transcoder transcoder = basist::ktx2_transcoder(&sel_codebook);
|
||||
|
||||
const void* dataPtr = reinterpret_cast<const void*>(data.c_str());
|
||||
auto dataSize = data.size();
|
||||
transcoder.init(dataPtr, dataSize);
|
||||
|
||||
auto header = transcoder.get_header();
|
||||
auto image_width = static_cast<uint32_t>(header.m_pixel_width);
|
||||
auto image_height = static_cast<uint32_t>(header.m_pixel_height);
|
||||
|
||||
transcoder.start_transcoding();
|
||||
auto buffer = std::vector<uint8_t>(image_width * image_height * 4);
|
||||
auto ok = transcoder.transcode_image_level(
|
||||
0 /* level_index */, 0 /* layer_index */, 0 /* face_index */, buffer.data(),
|
||||
buffer.size() / 4, basist::transcoder_texture_format::cTFRGBA32, 0 /* decode_flags */,
|
||||
image_width /* output_row_pitch_in_blocks_or_pixels */);
|
||||
if (!ok) {
|
||||
return val(std::string("Could not decode"));
|
||||
}
|
||||
|
||||
auto img_data_data = Uint8ClampedArray.new_(typed_memory_view(buffer.size(), buffer.data()));
|
||||
auto imgData = ImageData.new_(img_data_data, image_width, image_height);
|
||||
return imgData;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(my_module) {
|
||||
function("decode", &decode);
|
||||
}
|
7
codecs/basis/dec/basis_dec.d.ts
vendored
Normal file
7
codecs/basis/dec/basis_dec.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
export interface BasisModule extends EmscriptenWasm.Module {
|
||||
decode(data: BufferSource): ImageData | null;
|
||||
}
|
||||
|
||||
declare var moduleFactory: EmscriptenWasm.ModuleFactory<BasisModule>;
|
||||
|
||||
export default moduleFactory;
|
56
codecs/basis/dec/basis_dec.js
generated
Normal file
56
codecs/basis/dec/basis_dec.js
generated
Normal file
@ -0,0 +1,56 @@
|
||||
|
||||
var Module = (function() {
|
||||
var _scriptDir = import.meta.url;
|
||||
|
||||
return (
|
||||
function(Module) {
|
||||
Module = Module || {};
|
||||
|
||||
|
||||
var e;e||(e=typeof Module !== 'undefined' ? Module : {});var aa,r;e.ready=new Promise(function(a,b){aa=a;r=b});var t={},u;for(u in e)e.hasOwnProperty(u)&&(t[u]=e[u]);var v="",w;v=self.location.href;_scriptDir&&(v=_scriptDir);0!==v.indexOf("blob:")?v=v.substr(0,v.lastIndexOf("/")+1):v="";w=function(a){var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)};var ba=e.print||console.log.bind(console),y=e.printErr||console.warn.bind(console);
|
||||
for(u in t)t.hasOwnProperty(u)&&(e[u]=t[u]);t=null;var z;e.wasmBinary&&(z=e.wasmBinary);var noExitRuntime=e.noExitRuntime||!0;"object"!==typeof WebAssembly&&A("no native wasm support detected");var C,ca=!1,da=new TextDecoder("utf8");function D(a,b){if(!a)return"";b=a+b;for(var c=a;!(c>=b)&&E[c];)++c;return da.decode(E.subarray(a,c))}
|
||||
function ea(a,b,c){var d=E;if(0<c){c=b+c-1;for(var f=0;f<a.length;++f){var g=a.charCodeAt(f);if(55296<=g&&57343>=g){var k=a.charCodeAt(++f);g=65536+((g&1023)<<10)|k&1023}if(127>=g){if(b>=c)break;d[b++]=g}else{if(2047>=g){if(b+1>=c)break;d[b++]=192|g>>6}else{if(65535>=g){if(b+2>=c)break;d[b++]=224|g>>12}else{if(b+3>=c)break;d[b++]=240|g>>18;d[b++]=128|g>>12&63}d[b++]=128|g>>6&63}d[b++]=128|g&63}}d[b]=0}}var fa=new TextDecoder("utf-16le");
|
||||
function ha(a,b){var c=a>>1;for(b=c+b/2;!(c>=b)&&F[c];)++c;return fa.decode(E.subarray(a,c<<1))}function ia(a,b,c){void 0===c&&(c=2147483647);if(2>c)return 0;c-=2;var d=b;c=c<2*a.length?c/2:a.length;for(var f=0;f<c;++f)G[b>>1]=a.charCodeAt(f),b+=2;G[b>>1]=0;return b-d}function ja(a){return 2*a.length}function ka(a,b){for(var c=0,d="";!(c>=b/4);){var f=I[a+4*c>>2];if(0==f)break;++c;65536<=f?(f-=65536,d+=String.fromCharCode(55296|f>>10,56320|f&1023)):d+=String.fromCharCode(f)}return d}
|
||||
function la(a,b,c){void 0===c&&(c=2147483647);if(4>c)return 0;var d=b;c=d+c-4;for(var f=0;f<a.length;++f){var g=a.charCodeAt(f);if(55296<=g&&57343>=g){var k=a.charCodeAt(++f);g=65536+((g&1023)<<10)|k&1023}I[b>>2]=g;b+=4;if(b+4>c)break}I[b>>2]=0;return b-d}function ma(a){for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);55296<=d&&57343>=d&&++c;b+=4}return b}var na,oa,E,G,F,I,J,pa,qa;
|
||||
function ra(){var a=C.buffer;na=a;e.HEAP8=oa=new Int8Array(a);e.HEAP16=G=new Int16Array(a);e.HEAP32=I=new Int32Array(a);e.HEAPU8=E=new Uint8Array(a);e.HEAPU16=F=new Uint16Array(a);e.HEAPU32=J=new Uint32Array(a);e.HEAPF32=pa=new Float32Array(a);e.HEAPF64=qa=new Float64Array(a)}var L,sa=[],ta=[],ua=[];function va(){var a=e.preRun.shift();sa.unshift(a)}var M=0,wa=null,N=null;e.preloadedImages={};e.preloadedAudios={};
|
||||
function A(a){if(e.onAbort)e.onAbort(a);y(a);ca=!0;a=new WebAssembly.RuntimeError("abort("+a+"). Build with -s ASSERTIONS=1 for more info.");r(a);throw a;}var O=(new URL("basis_dec.wasm",import.meta.url)).toString();function xa(){try{if(O==O&&z)return new Uint8Array(z);if(w)return w(O);throw"both async and sync fetching of the wasm failed";}catch(a){A(a)}}
|
||||
function ya(){return z||"function"!==typeof fetch?Promise.resolve().then(function(){return xa()}):fetch(O,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+O+"'";return a.arrayBuffer()}).catch(function(){return xa()})}function za(a){for(;0<a.length;){var b=a.shift();if("function"==typeof b)b(e);else{var c=b.M;"number"===typeof c?void 0===b.I?L.get(c)():L.get(c)(b.I):c(void 0===b.I?null:b.I)}}}
|
||||
function Aa(a){switch(a){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+a);}}var Ba=void 0;function P(a){for(var b="";E[a];)b+=Ba[E[a++]];return b}var Q={},R={},S={};function Ca(a){if(void 0===a)return"_unknown";a=a.replace(/[^a-zA-Z0-9_]/g,"$");var b=a.charCodeAt(0);return 48<=b&&57>=b?"_"+a:a}
|
||||
function Da(a,b){a=Ca(a);return(new Function("body","return function "+a+'() {\n "use strict"; return body.apply(this, arguments);\n};\n'))(b)}function Ea(a){var b=Error,c=Da(a,function(d){this.name=a;this.message=d;d=Error(d).stack;void 0!==d&&(this.stack=this.toString()+"\n"+d.replace(/^Error(:[^\n]*)?\n/,""))});c.prototype=Object.create(b.prototype);c.prototype.constructor=c;c.prototype.toString=function(){return void 0===this.message?this.name:this.name+": "+this.message};return c}
|
||||
var Fa=void 0;function T(a){throw new Fa(a);}var Ga=void 0;function Ha(a,b){function c(h){h=b(h);if(h.length!==d.length)throw new Ga("Mismatched type converter count");for(var m=0;m<d.length;++m)U(d[m],h[m])}var d=[];d.forEach(function(h){S[h]=a});var f=Array(a.length),g=[],k=0;a.forEach(function(h,m){R.hasOwnProperty(h)?f[m]=R[h]:(g.push(h),Q.hasOwnProperty(h)||(Q[h]=[]),Q[h].push(function(){f[m]=R[h];++k;k===g.length&&c(f)}))});0===g.length&&c(f)}
|
||||
function U(a,b,c){c=c||{};if(!("argPackAdvance"in b))throw new TypeError("registerType registeredInstance requires argPackAdvance");var d=b.name;a||T('type "'+d+'" must have a positive integer typeid pointer');if(R.hasOwnProperty(a)){if(c.L)return;T("Cannot register type '"+d+"' twice")}R[a]=b;delete S[a];Q.hasOwnProperty(a)&&(b=Q[a],delete Q[a],b.forEach(function(f){f()}))}var Ia=[],V=[{},{value:void 0},{value:null},{value:!0},{value:!1}];
|
||||
function La(a){4<a&&0===--V[a].J&&(V[a]=void 0,Ia.push(a))}function W(a){switch(a){case void 0:return 1;case null:return 2;case !0:return 3;case !1:return 4;default:var b=Ia.length?Ia.pop():V.length;V[b]={J:1,value:a};return b}}function Ma(a){return this.fromWireType(J[a>>2])}function Na(a){if(null===a)return"null";var b=typeof a;return"object"===b||"array"===b||"function"===b?a.toString():""+a}
|
||||
function Oa(a,b){switch(b){case 2:return function(c){return this.fromWireType(pa[c>>2])};case 3:return function(c){return this.fromWireType(qa[c>>3])};default:throw new TypeError("Unknown float type: "+a);}}function Pa(a){var b=Function;if(!(b instanceof Function))throw new TypeError("new_ called with constructor type "+typeof b+" which is not a function");var c=Da(b.name||"unknownFunctionName",function(){});c.prototype=b.prototype;c=new c;a=b.apply(c,a);return a instanceof Object?a:c}
|
||||
function Qa(a){for(;a.length;){var b=a.pop();a.pop()(b)}}function Ra(a,b){var c=e;if(void 0===c[a].G){var d=c[a];c[a]=function(){c[a].G.hasOwnProperty(arguments.length)||T("Function '"+b+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+c[a].G+")!");return c[a].G[arguments.length].apply(this,arguments)};c[a].G=[];c[a].G[d.K]=d}}
|
||||
function Sa(a,b,c){e.hasOwnProperty(a)?((void 0===c||void 0!==e[a].G&&void 0!==e[a].G[c])&&T("Cannot register public name '"+a+"' twice"),Ra(a,a),e.hasOwnProperty(c)&&T("Cannot register multiple overloads of a function with the same number of arguments ("+c+")!"),e[a].G[c]=b):(e[a]=b,void 0!==c&&(e[a].O=c))}function Ta(a,b){for(var c=[],d=0;d<a;d++)c.push(I[(b>>2)+d]);return c}
|
||||
function Ua(a,b){var c=[];return function(){c.length=arguments.length;for(var d=0;d<arguments.length;d++)c[d]=arguments[d];a.includes("j")?(d=e["dynCall_"+a],d=c&&c.length?d.apply(null,[b].concat(c)):d.call(null,b)):d=L.get(b).apply(null,c);return d}}function Va(a,b){a=P(a);var c=a.includes("j")?Ua(a,b):L.get(b);"function"!==typeof c&&T("unknown function pointer with signature "+a+": "+b);return c}var Wa=void 0;function Xa(a){a=Ya(a);var b=P(a);X(a);return b}
|
||||
function Za(a,b){function c(g){f[g]||R[g]||(S[g]?S[g].forEach(c):(d.push(g),f[g]=!0))}var d=[],f={};b.forEach(c);throw new Wa(a+": "+d.map(Xa).join([", "]));}function $a(a,b,c){switch(b){case 0:return c?function(d){return oa[d]}:function(d){return E[d]};case 1:return c?function(d){return G[d>>1]}:function(d){return F[d>>1]};case 2:return c?function(d){return I[d>>2]}:function(d){return J[d>>2]};default:throw new TypeError("Unknown integer type: "+a);}}var ab={};
|
||||
function bb(){return"object"===typeof globalThis?globalThis:Function("return this")()}function cb(a,b){var c=R[a];void 0===c&&T(b+" has unknown type "+Xa(a));return c}for(var db={},eb=[null,[],[]],fb=Array(256),Y=0;256>Y;++Y)fb[Y]=String.fromCharCode(Y);Ba=fb;Fa=e.BindingError=Ea("BindingError");Ga=e.InternalError=Ea("InternalError");e.count_emval_handles=function(){for(var a=0,b=5;b<V.length;++b)void 0!==V[b]&&++a;return a};
|
||||
e.get_first_emval=function(){for(var a=5;a<V.length;++a)if(void 0!==V[a])return V[a];return null};Wa=e.UnboundTypeError=Ea("UnboundTypeError");
|
||||
var hb={a:function(a,b,c,d){A("Assertion failed: "+D(a)+", at: "+[b?D(b):"unknown filename",c,d?D(d):"unknown function"])},g:function(){},r:function(){},x:function(a,b,c,d,f){var g=Aa(c);b=P(b);U(a,{name:b,fromWireType:function(k){return!!k},toWireType:function(k,h){return h?d:f},argPackAdvance:8,readValueFromPointer:function(k){if(1===c)var h=oa;else if(2===c)h=G;else if(4===c)h=I;else throw new TypeError("Unknown boolean type size: "+b);return this.fromWireType(h[k>>g])},H:null})},w:function(a,
|
||||
b){b=P(b);U(a,{name:b,fromWireType:function(c){var d=V[c].value;La(c);return d},toWireType:function(c,d){return W(d)},argPackAdvance:8,readValueFromPointer:Ma,H:null})},l:function(a,b,c){c=Aa(c);b=P(b);U(a,{name:b,fromWireType:function(d){return d},toWireType:function(d,f){if("number"!==typeof f&&"boolean"!==typeof f)throw new TypeError('Cannot convert "'+Na(f)+'" to '+this.name);return f},argPackAdvance:8,readValueFromPointer:Oa(b,c),H:null})},o:function(a,b,c,d,f,g){var k=Ta(b,c);a=P(a);f=Va(d,
|
||||
f);Sa(a,function(){Za("Cannot call "+a+" due to unbound types",k)},b-1);Ha(k,function(h){var m=a,l=a;h=[h[0],null].concat(h.slice(1));var p=f,q=h.length;2>q&&T("argTypes array size mismatch! Must at least get return value and 'this' types!");for(var x=null!==h[1]&&!1,B=!1,n=1;n<h.length;++n)if(null!==h[n]&&void 0===h[n].H){B=!0;break}var Ja="void"!==h[0].name,H="",K="";for(n=0;n<q-2;++n)H+=(0!==n?", ":"")+"arg"+n,K+=(0!==n?", ":"")+"arg"+n+"Wired";l="return function "+Ca(l)+"("+H+") {\nif (arguments.length !== "+
|
||||
(q-2)+") {\nthrowBindingError('function "+l+" called with ' + arguments.length + ' arguments, expected "+(q-2)+" args!');\n}\n";B&&(l+="var destructors = [];\n");var Ka=B?"destructors":"null";H="throwBindingError invoker fn runDestructors retType classParam".split(" ");p=[T,p,g,Qa,h[0],h[1]];x&&(l+="var thisWired = classParam.toWireType("+Ka+", this);\n");for(n=0;n<q-2;++n)l+="var arg"+n+"Wired = argType"+n+".toWireType("+Ka+", arg"+n+"); // "+h[n+2].name+"\n",H.push("argType"+n),p.push(h[n+2]);x&&
|
||||
(K="thisWired"+(0<K.length?", ":"")+K);l+=(Ja?"var rv = ":"")+"invoker(fn"+(0<K.length?", ":"")+K+");\n";if(B)l+="runDestructors(destructors);\n";else for(n=x?1:2;n<h.length;++n)q=1===n?"thisWired":"arg"+(n-2)+"Wired",null!==h[n].H&&(l+=q+"_dtor("+q+"); // "+h[n].name+"\n",H.push(q+"_dtor"),p.push(h[n].H));Ja&&(l+="var ret = retType.fromWireType(rv);\nreturn ret;\n");H.push(l+"}\n");h=Pa(H).apply(null,p);n=b-1;if(!e.hasOwnProperty(m))throw new Ga("Replacing nonexistant public symbol");void 0!==e[m].G&&
|
||||
void 0!==n?e[m].G[n]=h:(e[m]=h,e[m].K=n);return[]})},c:function(a,b,c,d,f){function g(l){return l}b=P(b);-1===f&&(f=4294967295);var k=Aa(c);if(0===d){var h=32-8*c;g=function(l){return l<<h>>>h}}var m=b.includes("unsigned");U(a,{name:b,fromWireType:g,toWireType:function(l,p){if("number"!==typeof p&&"boolean"!==typeof p)throw new TypeError('Cannot convert "'+Na(p)+'" to '+this.name);if(p<d||p>f)throw new TypeError('Passing a number "'+Na(p)+'" from JS side to C/C++ side to an argument of type "'+b+
|
||||
'", which is outside the valid range ['+d+", "+f+"]!");return m?p>>>0:p|0},argPackAdvance:8,readValueFromPointer:$a(b,k,0!==d),H:null})},b:function(a,b,c){function d(g){g>>=2;var k=J;return new f(na,k[g+1],k[g])}var f=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array][b];c=P(c);U(a,{name:c,fromWireType:d,argPackAdvance:8,readValueFromPointer:d},{L:!0})},m:function(a,b){b=P(b);var c="std::string"===b;U(a,{name:b,fromWireType:function(d){var f=J[d>>2];if(c)for(var g=
|
||||
d+4,k=0;k<=f;++k){var h=d+4+k;if(k==f||0==E[h]){g=D(g,h-g);if(void 0===m)var m=g;else m+=String.fromCharCode(0),m+=g;g=h+1}}else{m=Array(f);for(k=0;k<f;++k)m[k]=String.fromCharCode(E[d+4+k]);m=m.join("")}X(d);return m},toWireType:function(d,f){f instanceof ArrayBuffer&&(f=new Uint8Array(f));var g="string"===typeof f;g||f instanceof Uint8Array||f instanceof Uint8ClampedArray||f instanceof Int8Array||T("Cannot pass non-string to std::string");var k=(c&&g?function(){for(var l=0,p=0;p<f.length;++p){var q=
|
||||
f.charCodeAt(p);55296<=q&&57343>=q&&(q=65536+((q&1023)<<10)|f.charCodeAt(++p)&1023);127>=q?++l:l=2047>=q?l+2:65535>=q?l+3:l+4}return l}:function(){return f.length})(),h=gb(4+k+1);J[h>>2]=k;if(c&&g)ea(f,h+4,k+1);else if(g)for(g=0;g<k;++g){var m=f.charCodeAt(g);255<m&&(X(h),T("String has UTF-16 code units that do not fit in 8 bits"));E[h+4+g]=m}else for(g=0;g<k;++g)E[h+4+g]=f[g];null!==d&&d.push(X,h);return h},argPackAdvance:8,readValueFromPointer:Ma,H:function(d){X(d)}})},i:function(a,b,c){c=P(c);
|
||||
if(2===b){var d=ha;var f=ia;var g=ja;var k=function(){return F};var h=1}else 4===b&&(d=ka,f=la,g=ma,k=function(){return J},h=2);U(a,{name:c,fromWireType:function(m){for(var l=J[m>>2],p=k(),q,x=m+4,B=0;B<=l;++B){var n=m+4+B*b;if(B==l||0==p[n>>h])x=d(x,n-x),void 0===q?q=x:(q+=String.fromCharCode(0),q+=x),x=n+b}X(m);return q},toWireType:function(m,l){"string"!==typeof l&&T("Cannot pass non-string to C++ string type "+c);var p=g(l),q=gb(4+p+b);J[q>>2]=p>>h;f(l,q+4,p+b);null!==m&&m.push(X,q);return q},
|
||||
argPackAdvance:8,readValueFromPointer:Ma,H:function(m){X(m)}})},n:function(a,b){b=P(b);U(a,{N:!0,name:b,argPackAdvance:0,fromWireType:function(){},toWireType:function(){}})},e:La,f:function(a){if(0===a)return W(bb());var b=ab[a];a=void 0===b?P(a):b;return W(bb()[a])},j:function(a){4<a&&(V[a].J+=1)},k:function(a,b,c,d){a||T("Cannot use deleted val. handle = "+a);a=V[a].value;var f=db[b];if(!f){f="";for(var g=0;g<b;++g)f+=(0!==g?", ":"")+"arg"+g;var k="return function emval_allocator_"+b+"(constructor, argTypes, args) {\n";
|
||||
for(g=0;g<b;++g)k+="var argType"+g+" = requireRegisteredType(Module['HEAP32'][(argTypes >>> 2) + "+g+'], "parameter '+g+'");\nvar arg'+g+" = argType"+g+".readValueFromPointer(args);\nargs += argType"+g+"['argPackAdvance'];\n";f=(new Function("requireRegisteredType","Module","__emval_register",k+("var obj = new constructor("+f+");\nreturn __emval_register(obj);\n}\n")))(cb,e,W);db[b]=f}return f(a,c,d)},p:function(a,b){a=cb(a,"_emval_take_value");a=a.readValueFromPointer(b);return W(a)},d:function(){A()},
|
||||
t:function(a,b,c){E.copyWithin(a,b,b+c)},h:function(a){var b=E.length;a>>>=0;if(2147483648<a)return!1;for(var c=1;4>=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,a+100663296);d=Math.max(a,d);0<d%65536&&(d+=65536-d%65536);a:{try{C.grow(Math.min(2147483648,d)-na.byteLength+65535>>>16);ra();var f=1;break a}catch(g){}f=void 0}if(f)return!0}return!1},v:function(){return 0},q:function(){},u:function(a,b,c,d){for(var f=0,g=0;g<c;g++){for(var k=I[b+8*g>>2],h=I[b+(8*g+4)>>2],m=0;m<h;m++){var l=E[k+m],p=eb[a];if(0===
|
||||
l||10===l){for(l=0;p[l]&&!(NaN<=l);)++l;l=da.decode(p.subarray?p.subarray(0,l):new Uint8Array(p.slice(0,l)));(1===a?ba:y)(l);p.length=0}else p.push(l)}f+=h}I[d>>2]=f;return 0},s:function(){}};
|
||||
(function(){function a(f){e.asm=f.exports;C=e.asm.y;ra();L=e.asm.E;ta.unshift(e.asm.z);M--;e.monitorRunDependencies&&e.monitorRunDependencies(M);0==M&&(null!==wa&&(clearInterval(wa),wa=null),N&&(f=N,N=null,f()))}function b(f){a(f.instance)}function c(f){return ya().then(function(g){return WebAssembly.instantiate(g,d)}).then(f,function(g){y("failed to asynchronously prepare wasm: "+g);A(g)})}var d={a:hb};M++;e.monitorRunDependencies&&e.monitorRunDependencies(M);if(e.instantiateWasm)try{return e.instantiateWasm(d,
|
||||
a)}catch(f){return y("Module.instantiateWasm callback failed with error: "+f),!1}(function(){return z||"function"!==typeof WebAssembly.instantiateStreaming||O.startsWith("data:application/octet-stream;base64,")||"function"!==typeof fetch?c(b):fetch(O,{credentials:"same-origin"}).then(function(f){return WebAssembly.instantiateStreaming(f,d).then(b,function(g){y("wasm streaming compile failed: "+g);y("falling back to ArrayBuffer instantiation");return c(b)})})})().catch(r);return{}})();
|
||||
e.___wasm_call_ctors=function(){return(e.___wasm_call_ctors=e.asm.z).apply(null,arguments)};var gb=e._malloc=function(){return(gb=e._malloc=e.asm.A).apply(null,arguments)},X=e._free=function(){return(X=e._free=e.asm.B).apply(null,arguments)},Ya=e.___getTypeName=function(){return(Ya=e.___getTypeName=e.asm.C).apply(null,arguments)};e.___embind_register_native_and_builtin_types=function(){return(e.___embind_register_native_and_builtin_types=e.asm.D).apply(null,arguments)};
|
||||
e.dynCall_jiji=function(){return(e.dynCall_jiji=e.asm.F).apply(null,arguments)};var Z;N=function ib(){Z||jb();Z||(N=ib)};
|
||||
function jb(){function a(){if(!Z&&(Z=!0,e.calledRun=!0,!ca)){za(ta);aa(e);if(e.onRuntimeInitialized)e.onRuntimeInitialized();if(e.postRun)for("function"==typeof e.postRun&&(e.postRun=[e.postRun]);e.postRun.length;){var b=e.postRun.shift();ua.unshift(b)}za(ua)}}if(!(0<M)){if(e.preRun)for("function"==typeof e.preRun&&(e.preRun=[e.preRun]);e.preRun.length;)va();za(sa);0<M||(e.setStatus?(e.setStatus("Running..."),setTimeout(function(){setTimeout(function(){e.setStatus("")},1);a()},1)):a())}}e.run=jb;
|
||||
if(e.preInit)for("function"==typeof e.preInit&&(e.preInit=[e.preInit]);0<e.preInit.length;)e.preInit.pop()();jb();
|
||||
|
||||
|
||||
return Module.ready
|
||||
}
|
||||
);
|
||||
})();
|
||||
export default Module;
|
BIN
codecs/basis/dec/basis_dec.wasm
Executable file
BIN
codecs/basis/dec/basis_dec.wasm
Executable file
Binary file not shown.
96
codecs/basis/enc/basis_enc.cpp
Normal file
96
codecs/basis/enc/basis_enc.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include <emscripten/bind.h>
|
||||
#include <emscripten/val.h>
|
||||
#include <inttypes.h>
|
||||
#include "basisu_comp.h"
|
||||
#include "basisu_enc.h"
|
||||
|
||||
using namespace emscripten;
|
||||
using namespace basisu;
|
||||
|
||||
struct BasisOptions {
|
||||
float quality;
|
||||
uint8_t compression;
|
||||
bool uastc;
|
||||
bool mipmap;
|
||||
bool srgb_mipmap;
|
||||
std::string mipmap_filter;
|
||||
bool perceptual;
|
||||
bool y_flip;
|
||||
uint32_t mipmap_min_dimension;
|
||||
};
|
||||
|
||||
thread_local const val Uint8Array = val::global("Uint8Array");
|
||||
|
||||
val encode(std::string image_in, int image_width, int image_height, BasisOptions opts) {
|
||||
basisu_encoder_init();
|
||||
|
||||
basist::etc1_global_selector_codebook sel_codebook(basist::g_global_selector_cb_size,
|
||||
basist::g_global_selector_cb);
|
||||
basis_compressor_params params;
|
||||
basis_compressor compressor;
|
||||
image img =
|
||||
image(reinterpret_cast<const uint8_t*>(image_in.c_str()), image_width, image_height, 4);
|
||||
// We don’t need the encoder to read/decode files from the filesystem
|
||||
params.m_read_source_images = false;
|
||||
// Writing is unnecessary, too
|
||||
params.m_write_output_basis_files = false;
|
||||
// No printf pls
|
||||
params.m_status_output = false;
|
||||
// True => UASTC, False => ETC1S
|
||||
params.m_uastc = opts.uastc;
|
||||
// Use the standardized KTX2 format
|
||||
params.m_create_ktx2_file = true;
|
||||
// Codebook, whatever this exactly is or does.
|
||||
params.m_pSel_codebook = &sel_codebook;
|
||||
// No multithreading. It apparently doesn’t work well in Wasm.
|
||||
// But we still need to provide a job pool.
|
||||
params.m_multithreading = false;
|
||||
job_pool jpool(1);
|
||||
params.m_pJob_pool = &jpool;
|
||||
|
||||
params.m_ktx2_uastc_supercompression = basist::KTX2_SS_ZSTANDARD;
|
||||
|
||||
params.m_perceptual = opts.perceptual;
|
||||
params.m_y_flip = opts.y_flip;
|
||||
params.m_mip_gen = opts.mipmap;
|
||||
params.m_mip_srgb = opts.srgb_mipmap;
|
||||
params.m_mip_filter = opts.mipmap_filter;
|
||||
params.m_mip_smallest_dimension = opts.mipmap_min_dimension;
|
||||
params.m_compression_level = opts.compression;
|
||||
params.m_source_images.push_back(img);
|
||||
|
||||
if (opts.uastc) {
|
||||
params.m_rdo_uastc_quality_scalar = opts.quality;
|
||||
params.m_rdo_uastc = opts.quality != 0;
|
||||
} else {
|
||||
params.m_quality_level = static_cast<int>(opts.quality);
|
||||
}
|
||||
|
||||
if (!compressor.init(params)) {
|
||||
return val(std::string("Well something went wrong during init"));
|
||||
}
|
||||
|
||||
if (compressor.process() != 0) {
|
||||
return val(std::string("Well something went wrong during processing"));
|
||||
}
|
||||
|
||||
auto comp_data = compressor.get_output_ktx2_file();
|
||||
auto js_result = Uint8Array.new_(typed_memory_view(comp_data.size(), &comp_data[0]));
|
||||
// Not sure if there is anything to free here
|
||||
return js_result;
|
||||
}
|
||||
|
||||
EMSCRIPTEN_BINDINGS(my_module) {
|
||||
value_object<BasisOptions>("BasisOptions")
|
||||
.field("quality", &BasisOptions::quality)
|
||||
.field("compression", &BasisOptions::compression)
|
||||
.field("uastc", &BasisOptions::uastc)
|
||||
.field("perceptual", &BasisOptions::perceptual)
|
||||
.field("y_flip", &BasisOptions::y_flip)
|
||||
.field("mipmap", &BasisOptions::mipmap)
|
||||
.field("srgb_mipmap", &BasisOptions::srgb_mipmap)
|
||||
.field("mipmap_filter", &BasisOptions::mipmap_filter)
|
||||
.field("mipmap_min_dimension", &BasisOptions::mipmap_min_dimension);
|
||||
|
||||
function("encode", &encode);
|
||||
}
|
24
codecs/basis/enc/basis_enc.d.ts
vendored
Normal file
24
codecs/basis/enc/basis_enc.d.ts
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
export interface EncodeOptions {
|
||||
quality: number;
|
||||
compression: number;
|
||||
uastc: boolean;
|
||||
y_flip: boolean;
|
||||
mipmap: boolean;
|
||||
srgb_mipmap: boolean;
|
||||
perceptual: boolean;
|
||||
mipmap_filter: string;
|
||||
mipmap_min_dimension: number;
|
||||
}
|
||||
|
||||
export interface BasisModule extends EmscriptenWasm.Module {
|
||||
encode(
|
||||
data: BufferSource,
|
||||
width: number,
|
||||
height: number,
|
||||
options: EncodeOptions,
|
||||
): Uint8Array | null;
|
||||
}
|
||||
|
||||
declare var moduleFactory: EmscriptenWasm.ModuleFactory<BasisModule>;
|
||||
|
||||
export default moduleFactory;
|
62
codecs/basis/enc/basis_enc.js
generated
Normal file
62
codecs/basis/enc/basis_enc.js
generated
Normal file
@ -0,0 +1,62 @@
|
||||
|
||||
var Module = (function() {
|
||||
var _scriptDir = import.meta.url;
|
||||
|
||||
return (
|
||||
function(Module) {
|
||||
Module = Module || {};
|
||||
|
||||
|
||||
var f;f||(f=typeof Module !== 'undefined' ? Module : {});var aa,ba;f.ready=new Promise(function(a,b){aa=a;ba=b});var r={},t;for(t in f)f.hasOwnProperty(t)&&(r[t]=f[t]);var u="",ca;u=self.location.href;_scriptDir&&(u=_scriptDir);0!==u.indexOf("blob:")?u=u.substr(0,u.lastIndexOf("/")+1):u="";ca=function(a){var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)};var da=f.print||console.log.bind(console),v=f.printErr||console.warn.bind(console);
|
||||
for(t in r)r.hasOwnProperty(t)&&(f[t]=r[t]);r=null;var ea=0,w;f.wasmBinary&&(w=f.wasmBinary);var noExitRuntime=f.noExitRuntime||!0;"object"!==typeof WebAssembly&&x("no native wasm support detected");var ha,ia=!1,ja=new TextDecoder("utf8");function B(a,b){if(!a)return"";b=a+b;for(var c=a;!(c>=b)&&C[c];)++c;return ja.decode(C.subarray(a,c))}
|
||||
function ka(a,b,c){var d=C;if(0<c){c=b+c-1;for(var e=0;e<a.length;++e){var g=a.charCodeAt(e);if(55296<=g&&57343>=g){var k=a.charCodeAt(++e);g=65536+((g&1023)<<10)|k&1023}if(127>=g){if(b>=c)break;d[b++]=g}else{if(2047>=g){if(b+1>=c)break;d[b++]=192|g>>6}else{if(65535>=g){if(b+2>=c)break;d[b++]=224|g>>12}else{if(b+3>=c)break;d[b++]=240|g>>18;d[b++]=128|g>>12&63}d[b++]=128|g>>6&63}d[b++]=128|g&63}}d[b]=0}}var la=new TextDecoder("utf-16le");
|
||||
function ma(a,b){var c=a>>1;for(b=c+b/2;!(c>=b)&&D[c];)++c;return la.decode(C.subarray(a,c<<1))}function na(a,b,c){void 0===c&&(c=2147483647);if(2>c)return 0;c-=2;var d=b;c=c<2*a.length?c/2:a.length;for(var e=0;e<c;++e)E[b>>1]=a.charCodeAt(e),b+=2;E[b>>1]=0;return b-d}function oa(a){return 2*a.length}function pa(a,b){for(var c=0,d="";!(c>=b/4);){var e=F[a+4*c>>2];if(0==e)break;++c;65536<=e?(e-=65536,d+=String.fromCharCode(55296|e>>10,56320|e&1023)):d+=String.fromCharCode(e)}return d}
|
||||
function qa(a,b,c){void 0===c&&(c=2147483647);if(4>c)return 0;var d=b;c=d+c-4;for(var e=0;e<a.length;++e){var g=a.charCodeAt(e);if(55296<=g&&57343>=g){var k=a.charCodeAt(++e);g=65536+((g&1023)<<10)|k&1023}F[b>>2]=g;b+=4;if(b+4>c)break}F[b>>2]=0;return b-d}function ra(a){for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);55296<=d&&57343>=d&&++c;b+=4}return b}var sa,H,C,E,D,F,I,ta,ua;
|
||||
function va(){var a=ha.buffer;sa=a;f.HEAP8=H=new Int8Array(a);f.HEAP16=E=new Int16Array(a);f.HEAP32=F=new Int32Array(a);f.HEAPU8=C=new Uint8Array(a);f.HEAPU16=D=new Uint16Array(a);f.HEAPU32=I=new Uint32Array(a);f.HEAPF32=ta=new Float32Array(a);f.HEAPF64=ua=new Float64Array(a)}var J,wa=[],xa=[],ya=[];function za(){var a=f.preRun.shift();wa.unshift(a)}var K=0,Aa=null,L=null;f.preloadedImages={};f.preloadedAudios={};
|
||||
function x(a){if(f.onAbort)f.onAbort(a);v(a);ia=!0;a=new WebAssembly.RuntimeError("abort("+a+"). Build with -s ASSERTIONS=1 for more info.");ba(a);throw a;}var M=(new URL("basis_enc.wasm",import.meta.url)).toString();function Ba(){try{if(M==M&&w)return new Uint8Array(w);if(ca)return ca(M);throw"both async and sync fetching of the wasm failed";}catch(a){x(a)}}
|
||||
function Ca(){return w||"function"!==typeof fetch?Promise.resolve().then(function(){return Ba()}):fetch(M,{credentials:"same-origin"}).then(function(a){if(!a.ok)throw"failed to load wasm binary file at '"+M+"'";return a.arrayBuffer()}).catch(function(){return Ba()})}function Da(a){for(;0<a.length;){var b=a.shift();if("function"==typeof b)b(f);else{var c=b.Aa;"number"===typeof c?void 0===b.fa?J.get(c)():J.get(c)(b.fa):c(void 0===b.fa?null:b.fa)}}}
|
||||
function Ea(a){this.ea=a-16;this.va=function(b){F[this.ea+8>>2]=b};this.sa=function(b){F[this.ea+0>>2]=b};this.ta=function(){F[this.ea+4>>2]=0};this.ra=function(){H[this.ea+12>>0]=0};this.ua=function(){H[this.ea+13>>0]=0};this.oa=function(b,c){this.va(b);this.sa(c);this.ta();this.ra();this.ua()}}var Fa=0,Ga=[null,[],[]],Ha={},Ia={};function Ja(a){for(;a.length;){var b=a.pop();a.pop()(b)}}function Ka(a){return this.fromWireType(I[a>>2])}var N={},O={},La={};
|
||||
function Ma(a){if(void 0===a)return"_unknown";a=a.replace(/[^a-zA-Z0-9_]/g,"$");var b=a.charCodeAt(0);return 48<=b&&57>=b?"_"+a:a}function Na(a,b){a=Ma(a);return(new Function("body","return function "+a+'() {\n "use strict"; return body.apply(this, arguments);\n};\n'))(b)}
|
||||
function Oa(a){var b=Error,c=Na(a,function(d){this.name=a;this.message=d;d=Error(d).stack;void 0!==d&&(this.stack=this.toString()+"\n"+d.replace(/^Error(:[^\n]*)?\n/,""))});c.prototype=Object.create(b.prototype);c.prototype.constructor=c;c.prototype.toString=function(){return void 0===this.message?this.name:this.name+": "+this.message};return c}var Pa=void 0;
|
||||
function Qa(a,b,c){function d(h){h=c(h);if(h.length!==a.length)throw new Pa("Mismatched type converter count");for(var l=0;l<a.length;++l)P(a[l],h[l])}a.forEach(function(h){La[h]=b});var e=Array(b.length),g=[],k=0;b.forEach(function(h,l){O.hasOwnProperty(h)?e[l]=O[h]:(g.push(h),N.hasOwnProperty(h)||(N[h]=[]),N[h].push(function(){e[l]=O[h];++k;k===g.length&&d(e)}))});0===g.length&&d(e)}
|
||||
function Ra(a){switch(a){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+a);}}var Sa=void 0;function Q(a){for(var b="";C[a];)b+=Sa[C[a++]];return b}var Ta=void 0;function R(a){throw new Ta(a);}
|
||||
function P(a,b,c){c=c||{};if(!("argPackAdvance"in b))throw new TypeError("registerType registeredInstance requires argPackAdvance");var d=b.name;a||R('type "'+d+'" must have a positive integer typeid pointer');if(O.hasOwnProperty(a)){if(c.na)return;R("Cannot register type '"+d+"' twice")}O[a]=b;delete La[a];N.hasOwnProperty(a)&&(b=N[a],delete N[a],b.forEach(function(e){e()}))}var Ua=[],S=[{},{value:void 0},{value:null},{value:!0},{value:!1}];
|
||||
function Va(a){4<a&&0===--S[a].ga&&(S[a]=void 0,Ua.push(a))}function T(a){switch(a){case void 0:return 1;case null:return 2;case !0:return 3;case !1:return 4;default:var b=Ua.length?Ua.pop():S.length;S[b]={ga:1,value:a};return b}}function Wa(a){if(null===a)return"null";var b=typeof a;return"object"===b||"array"===b||"function"===b?a.toString():""+a}
|
||||
function Xa(a,b){switch(b){case 2:return function(c){return this.fromWireType(ta[c>>2])};case 3:return function(c){return this.fromWireType(ua[c>>3])};default:throw new TypeError("Unknown float type: "+a);}}function Ya(a){var b=Function;if(!(b instanceof Function))throw new TypeError("new_ called with constructor type "+typeof b+" which is not a function");var c=Na(b.name||"unknownFunctionName",function(){});c.prototype=b.prototype;c=new c;a=b.apply(c,a);return a instanceof Object?a:c}
|
||||
function Za(a,b){var c=f;if(void 0===c[a].ca){var d=c[a];c[a]=function(){c[a].ca.hasOwnProperty(arguments.length)||R("Function '"+b+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+c[a].ca+")!");return c[a].ca[arguments.length].apply(this,arguments)};c[a].ca=[];c[a].ca[d.ia]=d}}
|
||||
function $a(a,b,c){f.hasOwnProperty(a)?((void 0===c||void 0!==f[a].ca&&void 0!==f[a].ca[c])&&R("Cannot register public name '"+a+"' twice"),Za(a,a),f.hasOwnProperty(c)&&R("Cannot register multiple overloads of a function with the same number of arguments ("+c+")!"),f[a].ca[c]=b):(f[a]=b,void 0!==c&&(f[a].Da=c))}function ab(a,b){for(var c=[],d=0;d<a;d++)c.push(F[(b>>2)+d]);return c}
|
||||
function bb(a,b){var c=[];return function(){c.length=arguments.length;for(var d=0;d<arguments.length;d++)c[d]=arguments[d];a.includes("j")?(d=f["dynCall_"+a],d=c&&c.length?d.apply(null,[b].concat(c)):d.call(null,b)):d=J.get(b).apply(null,c);return d}}function U(a,b){a=Q(a);var c=a.includes("j")?bb(a,b):J.get(b);"function"!==typeof c&&R("unknown function pointer with signature "+a+": "+b);return c}var cb=void 0;function db(a){a=eb(a);var b=Q(a);V(a);return b}
|
||||
function fb(a,b){function c(g){e[g]||O[g]||(La[g]?La[g].forEach(c):(d.push(g),e[g]=!0))}var d=[],e={};b.forEach(c);throw new cb(a+": "+d.map(db).join([", "]));}function gb(a,b,c){switch(b){case 0:return c?function(d){return H[d]}:function(d){return C[d]};case 1:return c?function(d){return E[d>>1]}:function(d){return D[d>>1]};case 2:return c?function(d){return F[d>>2]}:function(d){return I[d>>2]};default:throw new TypeError("Unknown integer type: "+a);}}var hb={};
|
||||
function ib(){return"object"===typeof globalThis?globalThis:Function("return this")()}function jb(a,b){var c=O[a];void 0===c&&R(b+" has unknown type "+db(a));return c}var kb={};Pa=f.InternalError=Oa("InternalError");for(var lb=Array(256),mb=0;256>mb;++mb)lb[mb]=String.fromCharCode(mb);Sa=lb;Ta=f.BindingError=Oa("BindingError");f.count_emval_handles=function(){for(var a=0,b=5;b<S.length;++b)void 0!==S[b]&&++a;return a};
|
||||
f.get_first_emval=function(){for(var a=5;a<S.length;++a)if(void 0!==S[a])return S[a];return null};cb=f.UnboundTypeError=Oa("UnboundTypeError");
|
||||
var vb={a:function(a,b,c,d){x("Assertion failed: "+B(a)+", at: "+[b?B(b):"unknown filename",c,d?B(d):"unknown function"])},D:function(a){return nb(a+16)+16},R:function(){},z:function(a,b,c){(new Ea(a)).oa(b,c);Fa++;throw a;},s:function(){return 0},H:function(){return 0},I:function(){},P:function(a){var b=Ia[a];delete Ia[a];var c=b.pa,d=b.qa,e=b.ha,g=e.map(function(k){return k.ma}).concat(e.map(function(k){return k.xa}));Qa([a],g,function(k){var h={};e.forEach(function(l,m){var n=k[m],q=l.ka,y=l.la,
|
||||
z=k[m+e.length],p=l.wa,fa=l.ya;h[l.ja]={read:function(A){return n.fromWireType(q(y,A))},write:function(A,G){var X=[];p(fa,A,z.toWireType(X,G));Ja(X)}}});return[{name:b.name,fromWireType:function(l){var m={},n;for(n in h)m[n]=h[n].read(l);d(l);return m},toWireType:function(l,m){for(var n in h)if(!(n in m))throw new TypeError('Missing field: "'+n+'"');var q=c();for(n in h)h[n].write(q,m[n]);null!==l&&l.push(d,q);return q},argPackAdvance:8,readValueFromPointer:Ka,da:d}]})},B:function(){},L:function(a,
|
||||
b,c,d,e){var g=Ra(c);b=Q(b);P(a,{name:b,fromWireType:function(k){return!!k},toWireType:function(k,h){return h?d:e},argPackAdvance:8,readValueFromPointer:function(k){if(1===c)var h=H;else if(2===c)h=E;else if(4===c)h=F;else throw new TypeError("Unknown boolean type size: "+b);return this.fromWireType(h[k>>g])},da:null})},K:function(a,b){b=Q(b);P(a,{name:b,fromWireType:function(c){var d=S[c].value;Va(c);return d},toWireType:function(c,d){return T(d)},argPackAdvance:8,readValueFromPointer:Ka,da:null})},
|
||||
u:function(a,b,c){c=Ra(c);b=Q(b);P(a,{name:b,fromWireType:function(d){return d},toWireType:function(d,e){if("number"!==typeof e&&"boolean"!==typeof e)throw new TypeError('Cannot convert "'+Wa(e)+'" to '+this.name);return e},argPackAdvance:8,readValueFromPointer:Xa(b,c),da:null})},O:function(a,b,c,d,e,g){var k=ab(b,c);a=Q(a);e=U(d,e);$a(a,function(){fb("Cannot call "+a+" due to unbound types",k)},b-1);Qa([],k,function(h){var l=a,m=a;h=[h[0],null].concat(h.slice(1));var n=e,q=h.length;2>q&&R("argTypes array size mismatch! Must at least get return value and 'this' types!");
|
||||
for(var y=null!==h[1]&&!1,z=!1,p=1;p<h.length;++p)if(null!==h[p]&&void 0===h[p].da){z=!0;break}var fa="void"!==h[0].name,A="",G="";for(p=0;p<q-2;++p)A+=(0!==p?", ":"")+"arg"+p,G+=(0!==p?", ":"")+"arg"+p+"Wired";m="return function "+Ma(m)+"("+A+") {\nif (arguments.length !== "+(q-2)+") {\nthrowBindingError('function "+m+" called with ' + arguments.length + ' arguments, expected "+(q-2)+" args!');\n}\n";z&&(m+="var destructors = [];\n");var X=z?"destructors":"null";A="throwBindingError invoker fn runDestructors retType classParam".split(" ");
|
||||
n=[R,n,g,Ja,h[0],h[1]];y&&(m+="var thisWired = classParam.toWireType("+X+", this);\n");for(p=0;p<q-2;++p)m+="var arg"+p+"Wired = argType"+p+".toWireType("+X+", arg"+p+"); // "+h[p+2].name+"\n",A.push("argType"+p),n.push(h[p+2]);y&&(G="thisWired"+(0<G.length?", ":"")+G);m+=(fa?"var rv = ":"")+"invoker(fn"+(0<G.length?", ":"")+G+");\n";if(z)m+="runDestructors(destructors);\n";else for(p=y?1:2;p<h.length;++p)q=1===p?"thisWired":"arg"+(p-2)+"Wired",null!==h[p].da&&(m+=q+"_dtor("+q+"); // "+h[p].name+
|
||||
"\n",A.push(q+"_dtor"),n.push(h[p].da));fa&&(m+="var ret = retType.fromWireType(rv);\nreturn ret;\n");A.push(m+"}\n");h=Ya(A).apply(null,n);p=b-1;if(!f.hasOwnProperty(l))throw new Pa("Replacing nonexistant public symbol");void 0!==f[l].ca&&void 0!==p?f[l].ca[p]=h:(f[l]=h,f[l].ia=p);return[]})},j:function(a,b,c,d,e){function g(m){return m}b=Q(b);-1===e&&(e=4294967295);var k=Ra(c);if(0===d){var h=32-8*c;g=function(m){return m<<h>>>h}}var l=b.includes("unsigned");P(a,{name:b,fromWireType:g,toWireType:function(m,
|
||||
n){if("number"!==typeof n&&"boolean"!==typeof n)throw new TypeError('Cannot convert "'+Wa(n)+'" to '+this.name);if(n<d||n>e)throw new TypeError('Passing a number "'+Wa(n)+'" from JS side to C/C++ side to an argument of type "'+b+'", which is outside the valid range ['+d+", "+e+"]!");return l?n>>>0:n|0},argPackAdvance:8,readValueFromPointer:gb(b,k,0!==d),da:null})},i:function(a,b,c){function d(g){g>>=2;var k=I;return new e(sa,k[g+1],k[g])}var e=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,
|
||||
Uint32Array,Float32Array,Float64Array][b];c=Q(c);P(a,{name:c,fromWireType:d,argPackAdvance:8,readValueFromPointer:d},{na:!0})},v:function(a,b){b=Q(b);var c="std::string"===b;P(a,{name:b,fromWireType:function(d){var e=I[d>>2];if(c)for(var g=d+4,k=0;k<=e;++k){var h=d+4+k;if(k==e||0==C[h]){g=B(g,h-g);if(void 0===l)var l=g;else l+=String.fromCharCode(0),l+=g;g=h+1}}else{l=Array(e);for(k=0;k<e;++k)l[k]=String.fromCharCode(C[d+4+k]);l=l.join("")}V(d);return l},toWireType:function(d,e){e instanceof ArrayBuffer&&
|
||||
(e=new Uint8Array(e));var g="string"===typeof e;g||e instanceof Uint8Array||e instanceof Uint8ClampedArray||e instanceof Int8Array||R("Cannot pass non-string to std::string");var k=(c&&g?function(){for(var m=0,n=0;n<e.length;++n){var q=e.charCodeAt(n);55296<=q&&57343>=q&&(q=65536+((q&1023)<<10)|e.charCodeAt(++n)&1023);127>=q?++m:m=2047>=q?m+2:65535>=q?m+3:m+4}return m}:function(){return e.length})(),h=nb(4+k+1);I[h>>2]=k;if(c&&g)ka(e,h+4,k+1);else if(g)for(g=0;g<k;++g){var l=e.charCodeAt(g);255<l&&
|
||||
(V(h),R("String has UTF-16 code units that do not fit in 8 bits"));C[h+4+g]=l}else for(g=0;g<k;++g)C[h+4+g]=e[g];null!==d&&d.push(V,h);return h},argPackAdvance:8,readValueFromPointer:Ka,da:function(d){V(d)}})},p:function(a,b,c){c=Q(c);if(2===b){var d=ma;var e=na;var g=oa;var k=function(){return D};var h=1}else 4===b&&(d=pa,e=qa,g=ra,k=function(){return I},h=2);P(a,{name:c,fromWireType:function(l){for(var m=I[l>>2],n=k(),q,y=l+4,z=0;z<=m;++z){var p=l+4+z*b;if(z==m||0==n[p>>h])y=d(y,p-y),void 0===q?
|
||||
q=y:(q+=String.fromCharCode(0),q+=y),y=p+b}V(l);return q},toWireType:function(l,m){"string"!==typeof m&&R("Cannot pass non-string to C++ string type "+c);var n=g(m),q=nb(4+n+b);I[q>>2]=n>>h;e(m,q+4,n+b);null!==l&&l.push(V,q);return q},argPackAdvance:8,readValueFromPointer:Ka,da:function(l){V(l)}})},Q:function(a,b,c,d,e,g){Ia[a]={name:Q(b),pa:U(c,d),qa:U(e,g),ha:[]}},k:function(a,b,c,d,e,g,k,h,l,m){Ia[a].ha.push({ja:Q(b),ma:c,ka:U(d,e),la:g,xa:k,wa:U(h,l),ya:m})},M:function(a,b){b=Q(b);P(a,{Ca:!0,
|
||||
name:b,argPackAdvance:0,fromWireType:function(){},toWireType:function(){}})},m:Va,y:function(a){if(0===a)return T(ib());var b=hb[a];a=void 0===b?Q(a):b;return T(ib()[a])},N:function(a){4<a&&(S[a].ga+=1)},w:function(a,b,c,d){a||R("Cannot use deleted val. handle = "+a);a=S[a].value;var e=kb[b];if(!e){e="";for(var g=0;g<b;++g)e+=(0!==g?", ":"")+"arg"+g;var k="return function emval_allocator_"+b+"(constructor, argTypes, args) {\n";for(g=0;g<b;++g)k+="var argType"+g+" = requireRegisteredType(Module['HEAP32'][(argTypes >>> 2) + "+
|
||||
g+'], "parameter '+g+'");\nvar arg'+g+" = argType"+g+".readValueFromPointer(args);\nargs += argType"+g+"['argPackAdvance'];\n";e=(new Function("requireRegisteredType","Module","__emval_register",k+("var obj = new constructor("+e+");\nreturn __emval_register(obj);\n}\n")))(jb,f,T);kb[b]=e}return e(a,c,d)},x:function(a,b){a=jb(a,"_emval_take_value");a=a.readValueFromPointer(b);return T(a)},e:function(){x()},h:function(a,b){W(a,b||1);throw"longjmp";},E:function(a,b,c){C.copyWithin(a,b,b+c)},o:function(a){var b=
|
||||
C.length;a>>>=0;if(2147483648<a)return!1;for(var c=1;4>=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,a+100663296);d=Math.max(a,d);0<d%65536&&(d+=65536-d%65536);a:{try{ha.grow(Math.min(2147483648,d)-sa.byteLength+65535>>>16);va();var e=1;break a}catch(g){}e=void 0}if(e)return!0}return!1},t:function(){return 0},G:function(a,b,c,d){a=Ha.Ba(a);b=Ha.za(a,b,c);F[d>>2]=b;return 0},A:function(){},J:function(a,b,c,d){for(var e=0,g=0;g<c;g++){for(var k=F[b+8*g>>2],h=F[b+(8*g+4)>>2],l=0;l<h;l++){var m=C[k+l],n=Ga[a];
|
||||
if(0===m||10===m){for(m=0;n[m]&&!(NaN<=m);)++m;m=ja.decode(n.subarray?n.subarray(0,m):new Uint8Array(n.slice(0,m)));(1===a?da:v)(m);n.length=0}else n.push(m)}e+=h}F[d>>2]=e;return 0},c:function(){return ea},g:function(a){var b=Date.now();F[a>>2]=b/1E3|0;F[a+4>>2]=b%1E3*1E3|0;return 0},l:ob,f:pb,r:qb,q:rb,n:sb,d:tb,C:ub,F:function(){return 28},b:function(a){ea=a}};
|
||||
(function(){function a(e){f.asm=e.exports;ha=f.asm.S;va();J=f.asm.$;xa.unshift(f.asm.T);K--;f.monitorRunDependencies&&f.monitorRunDependencies(K);0==K&&(null!==Aa&&(clearInterval(Aa),Aa=null),L&&(e=L,L=null,e()))}function b(e){a(e.instance)}function c(e){return Ca().then(function(g){return WebAssembly.instantiate(g,d)}).then(e,function(g){v("failed to asynchronously prepare wasm: "+g);x(g)})}var d={a:vb};K++;f.monitorRunDependencies&&f.monitorRunDependencies(K);if(f.instantiateWasm)try{return f.instantiateWasm(d,
|
||||
a)}catch(e){return v("Module.instantiateWasm callback failed with error: "+e),!1}(function(){return w||"function"!==typeof WebAssembly.instantiateStreaming||M.startsWith("data:application/octet-stream;base64,")||"function"!==typeof fetch?c(b):fetch(M,{credentials:"same-origin"}).then(function(e){return WebAssembly.instantiateStreaming(e,d).then(b,function(g){v("wasm streaming compile failed: "+g);v("falling back to ArrayBuffer instantiation");return c(b)})})})().catch(ba);return{}})();
|
||||
f.___wasm_call_ctors=function(){return(f.___wasm_call_ctors=f.asm.T).apply(null,arguments)};var nb=f._malloc=function(){return(nb=f._malloc=f.asm.U).apply(null,arguments)},V=f._free=function(){return(V=f._free=f.asm.V).apply(null,arguments)},eb=f.___getTypeName=function(){return(eb=f.___getTypeName=f.asm.W).apply(null,arguments)};f.___embind_register_native_and_builtin_types=function(){return(f.___embind_register_native_and_builtin_types=f.asm.X).apply(null,arguments)};
|
||||
var Y=f.stackSave=function(){return(Y=f.stackSave=f.asm.Y).apply(null,arguments)},Z=f.stackRestore=function(){return(Z=f.stackRestore=f.asm.Z).apply(null,arguments)},W=f._setThrew=function(){return(W=f._setThrew=f.asm._).apply(null,arguments)};f.dynCall_jiiii=function(){return(f.dynCall_jiiii=f.asm.aa).apply(null,arguments)};f.dynCall_jiji=function(){return(f.dynCall_jiji=f.asm.ba).apply(null,arguments)};
|
||||
function tb(a,b,c){var d=Y();try{J.get(a)(b,c)}catch(e){Z(d);if(e!==e+0&&"longjmp"!==e)throw e;W(1,0)}}function sb(a,b){var c=Y();try{J.get(a)(b)}catch(d){Z(c);if(d!==d+0&&"longjmp"!==d)throw d;W(1,0)}}function qb(a,b,c,d){var e=Y();try{return J.get(a)(b,c,d)}catch(g){Z(e);if(g!==g+0&&"longjmp"!==g)throw g;W(1,0)}}function pb(a,b,c){var d=Y();try{return J.get(a)(b,c)}catch(e){Z(d);if(e!==e+0&&"longjmp"!==e)throw e;W(1,0)}}
|
||||
function ob(a,b){var c=Y();try{return J.get(a)(b)}catch(d){Z(c);if(d!==d+0&&"longjmp"!==d)throw d;W(1,0)}}function rb(a,b,c,d,e,g){var k=Y();try{return J.get(a)(b,c,d,e,g)}catch(h){Z(k);if(h!==h+0&&"longjmp"!==h)throw h;W(1,0)}}function ub(a,b,c,d,e){var g=Y();try{J.get(a)(b,c,d,e)}catch(k){Z(g);if(k!==k+0&&"longjmp"!==k)throw k;W(1,0)}}var wb;L=function xb(){wb||yb();wb||(L=xb)};
|
||||
function yb(){function a(){if(!wb&&(wb=!0,f.calledRun=!0,!ia)){Da(xa);aa(f);if(f.onRuntimeInitialized)f.onRuntimeInitialized();if(f.postRun)for("function"==typeof f.postRun&&(f.postRun=[f.postRun]);f.postRun.length;){var b=f.postRun.shift();ya.unshift(b)}Da(ya)}}if(!(0<K)){if(f.preRun)for("function"==typeof f.preRun&&(f.preRun=[f.preRun]);f.preRun.length;)za();Da(wa);0<K||(f.setStatus?(f.setStatus("Running..."),setTimeout(function(){setTimeout(function(){f.setStatus("")},1);a()},1)):a())}}f.run=yb;
|
||||
if(f.preInit)for("function"==typeof f.preInit&&(f.preInit=[f.preInit]);0<f.preInit.length;)f.preInit.pop()();yb();
|
||||
|
||||
|
||||
return Module.ready
|
||||
}
|
||||
);
|
||||
})();
|
||||
export default Module;
|
BIN
codecs/basis/enc/basis_enc.wasm
Executable file
BIN
codecs/basis/enc/basis_enc.wasm
Executable file
Binary file not shown.
6
codecs/basis/package.json
Normal file
6
codecs/basis/package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "../build-cpp.sh"
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
CODEC_URL = https://gitlab.com/wg1/jpeg-xl.git
|
||||
CODEC_VERSION = ab7c5e9b6795134377aa4846ceaae2c5bc504f76
|
||||
CODEC_URL = https://github.com/libjxl/libjxl.git
|
||||
CODEC_VERSION = v0.5
|
||||
CODEC_DIR = node_modules/jxl
|
||||
CODEC_BUILD_ROOT := $(CODEC_DIR)/build
|
||||
CODEC_MT_BUILD_DIR := $(CODEC_BUILD_ROOT)/mt
|
||||
|
Binary file not shown.
Binary file not shown.
@ -35,13 +35,15 @@ val encode(std::string image, int width, int height, JXLOptions options) {
|
||||
|
||||
cparams.epf = options.epf;
|
||||
cparams.speed_tier = static_cast<jxl::SpeedTier>(options.speed);
|
||||
cparams.near_lossless = options.nearLossless;
|
||||
cparams.decoding_speed_tier = options.decodingSpeedTier;
|
||||
|
||||
if (options.lossyPalette) {
|
||||
if (options.lossyPalette || options.nearLossless) {
|
||||
cparams.lossy_palette = true;
|
||||
cparams.palette_colors = 0;
|
||||
cparams.options.predictor = jxl::Predictor::Zero;
|
||||
// Near-lossless assumes -R 0
|
||||
cparams.responsive = 0;
|
||||
cparams.modular_mode = true;
|
||||
}
|
||||
|
||||
float quality = options.quality;
|
||||
@ -77,12 +79,6 @@ val encode(std::string image, int width, int height, JXLOptions options) {
|
||||
}
|
||||
}
|
||||
|
||||
if (cparams.near_lossless) {
|
||||
// Near-lossless assumes -R 0
|
||||
cparams.responsive = 0;
|
||||
cparams.modular_mode = true;
|
||||
}
|
||||
|
||||
io.metadata.m.SetAlphaBits(8);
|
||||
if (!io.metadata.size.Set(width, height)) {
|
||||
return val::null();
|
||||
|
Binary file not shown.
2
codecs/jxl/enc/jxl_enc_mt.js
generated
2
codecs/jxl/enc/jxl_enc_mt.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
2
codecs/jxl/enc/jxl_enc_mt_simd.js
generated
2
codecs/jxl/enc/jxl_enc_mt_simd.js
generated
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@ -158,6 +158,11 @@ val encode(std::string image_in, int image_width, int image_height, MozJpegOptio
|
||||
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.chroma_subsample > 2) {
|
||||
// Otherwise encoding fails.
|
||||
jpeg_c_set_int_param(&cinfo, JINT_DC_SCAN_OPT_MODE, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts.baseline && opts.progressive) {
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -53,7 +53,11 @@ where
|
||||
#[wasm_bindgen]
|
||||
pub fn decode(mut data: &[u8]) -> ImageData {
|
||||
let mut decoder = png::Decoder::new(&mut data);
|
||||
decoder.set_transformations(png::Transformations::EXPAND);
|
||||
decoder.set_transformations(
|
||||
png::Transformations::EXPAND | // Turn paletted images into RGB
|
||||
png::Transformations::PACKING | // Turn images <8bit to 8bit
|
||||
png::Transformations::STRIP_16, // Turn 16bit into 8 bit
|
||||
);
|
||||
let (info, mut reader) = decoder.read_info().unwrap_throw();
|
||||
let num_pixels = (info.width * info.height) as usize;
|
||||
let mut buf = vec![0; num_pixels * 4];
|
||||
|
14
codecs/visdif/BUILD.md
Normal file
14
codecs/visdif/BUILD.md
Normal file
@ -0,0 +1,14 @@
|
||||
This codec currently needs monkey-patching of Emscripten
|
||||
|
||||
```
|
||||
$ docker run --rm -it -v $(PWD):/src squoosh-cpp "/bin/bash"
|
||||
# cat << EOF | patch /emsdk/upstream/emscripten/system/lib/dlmalloc.c
|
||||
659c659
|
||||
< #define MALLOC_ALIGNMENT ((size_t)(2 * sizeof(void *)))
|
||||
---
|
||||
> #define MALLOC_ALIGNMENT ((size_t)(16U))
|
||||
EOF
|
||||
# emcc --clear-cache
|
||||
# /emsdk/upstream/emscripten/embuilder build libdlmalloc --force
|
||||
# emmake make
|
||||
```
|
@ -1,22 +0,0 @@
|
||||
|
||||
import {dirname} from "path";
|
||||
globalThis.__dirname = dirname(import.meta.url);
|
||||
import { createRequire } from 'module';
|
||||
|
||||
globalThis.require = createRequire(import.meta.url);
|
||||
import visdif from './visdif.js';
|
||||
|
||||
const {VisDiff} = await visdif({
|
||||
locateFile() {
|
||||
return new URL("./visdif.wasm", import.meta.url).pathname;
|
||||
}
|
||||
});
|
||||
|
||||
const comparator = new VisDiff(
|
||||
new Uint8ClampedArray([0, 0, 0, 255]),
|
||||
1,
|
||||
1
|
||||
);
|
||||
|
||||
const distance = comparator.distance(new Uint8ClampedArray([1,1,1,255]));
|
||||
console.log({distance});
|
@ -5,12 +5,19 @@
|
||||
using namespace emscripten;
|
||||
using namespace butteraugli;
|
||||
|
||||
#define GAMMA 2.2
|
||||
|
||||
static float SrgbToLinear[256];
|
||||
|
||||
inline void gammaLookupTable() {
|
||||
SrgbToLinear[0] = 0;
|
||||
for (int i = 1; i < 256; ++i) {
|
||||
SrgbToLinear[i] = static_cast<float>(255.0 * pow(i / 255.0, GAMMA));
|
||||
}
|
||||
}
|
||||
|
||||
// Turns an interleaved RGBA buffer into 4 planes for each color channel
|
||||
void planarize(std::vector<ImageF>& img,
|
||||
const uint8_t* rgba,
|
||||
int width,
|
||||
int height,
|
||||
float gamma = 2.2) {
|
||||
void planarize(std::vector<ImageF>& img, const uint8_t* rgba, int width, int height) {
|
||||
assert(img.size() == 0);
|
||||
img.push_back(ImageF(width, height));
|
||||
img.push_back(ImageF(width, height));
|
||||
@ -22,10 +29,10 @@ void planarize(std::vector<ImageF>& img,
|
||||
float* const row_b = img[2].Row(y);
|
||||
float* const row_a = img[3].Row(y);
|
||||
for (int x = 0; x < width; x++) {
|
||||
row_r[x] = 255.0 * pow(rgba[(y * width + x) * 4 + 0] / 255.0, gamma);
|
||||
row_g[x] = 255.0 * pow(rgba[(y * width + x) * 4 + 1] / 255.0, gamma);
|
||||
row_b[x] = 255.0 * pow(rgba[(y * width + x) * 4 + 2] / 255.0, gamma);
|
||||
row_a[x] = 255.0 * pow(rgba[(y * width + x) * 4 + 3] / 255.0, gamma);
|
||||
row_r[x] = SrgbToLinear[rgba[(y * width + x) * 4 + 0]];
|
||||
row_g[x] = SrgbToLinear[rgba[(y * width + x) * 4 + 1]];
|
||||
row_b[x] = SrgbToLinear[rgba[(y * width + x) * 4 + 2]];
|
||||
row_a[x] = SrgbToLinear[rgba[(y * width + x) * 4 + 3]];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -37,6 +44,7 @@ class VisDiff {
|
||||
|
||||
public:
|
||||
VisDiff(std::string ref_img, int width, int height) {
|
||||
gammaLookupTable();
|
||||
planarize(this->ref_img, (uint8_t*)ref_img.c_str(), width, height);
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
|
@ -1,21 +0,0 @@
|
||||
import {dirname} from "path";
|
||||
globalThis.__dirname = dirname(import.meta.url);
|
||||
import { createRequire } from 'module';
|
||||
|
||||
globalThis.require = createRequire(import.meta.url);
|
||||
import visdif from './visdif.js';
|
||||
|
||||
const {VisDiff} = await visdif({
|
||||
locateFile() {
|
||||
return new URL("./visdif.wasm", import.meta.url).pathname;
|
||||
}
|
||||
});
|
||||
|
||||
const comparator = new VisDiff(
|
||||
new Uint8ClampedArray([0, 0, 0, 255]),
|
||||
1,
|
||||
1
|
||||
);
|
||||
|
||||
const distance = comparator.distance(new Uint8ClampedArray([1,1,1,255]));
|
||||
console.log({distance});
|
Binary file not shown.
@ -77,7 +77,9 @@ export default function entryDataPlugin() {
|
||||
}
|
||||
|
||||
return JSON.stringify(
|
||||
getDependencies(chunks, chunk).map((filename) => fileNameToURL(filename)),
|
||||
getDependencies(chunks, chunk).map((filename) =>
|
||||
fileNameToURL(filename),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -30,7 +30,7 @@ const imagePath = 'path/to/image.png';
|
||||
const image = imagePool.ingestImage(imagePath);
|
||||
```
|
||||
|
||||
The `ingestImage` function can take anything the node [`readFile`][readfile] function can take, uncluding a buffer and `FileHandle`.
|
||||
The `ingestImage` function can take anything the node [`readFile`][readfile] function can take, including a buffer and `FileHandle`.
|
||||
|
||||
The returned `image` object is a representation of the original image, that you can now preprocess, encode, and extract information about.
|
||||
|
||||
@ -42,11 +42,19 @@ When an image has been ingested, you can start preprocessing it and encoding it
|
||||
await image.decoded; //Wait until the image is decoded before running preprocessors.
|
||||
|
||||
const preprocessOptions = {
|
||||
//When both width and height are specified, the image resized to specified size.
|
||||
resize: {
|
||||
enabled: true,
|
||||
width: 100,
|
||||
height: 50,
|
||||
}
|
||||
/*
|
||||
//When either width or height is specified, the image resized to specified size keeping aspect ratio.
|
||||
resize: {
|
||||
enabled: true,
|
||||
width: 100,
|
||||
}
|
||||
*/
|
||||
}
|
||||
await image.preprocess(preprocessOptions);
|
||||
|
||||
@ -60,7 +68,7 @@ await image.encode(encodeOptions);
|
||||
|
||||
```
|
||||
|
||||
The default values for each option can be found in the [`codecs.js`][codecs.js] file under `defaultEncoderOptions`. Every unspecified value will use the default value specified there. _Better documentation is needed here._
|
||||
The default values for each option can be found in the [`codecs.ts`][codecs.ts] file under `defaultEncoderOptions`. Every unspecified value will use the default value specified there. _Better documentation is needed here._
|
||||
|
||||
You can run your own code inbetween the different steps, if, for example, you want to change how much the image should be resized based on its original height. (See [Extracting image information](#extracting-image-information) to learn how to get the image dimensions).
|
||||
|
||||
@ -158,6 +166,6 @@ const encodeOptions: {
|
||||
```
|
||||
|
||||
[squoosh]: https://squoosh.app
|
||||
[codecs.js]: https://github.com/GoogleChromeLabs/squoosh/blob/dev/libsquoosh/src/codecs.js
|
||||
[codecs.ts]: https://github.com/GoogleChromeLabs/squoosh/blob/dev/libsquoosh/src/codecs.ts
|
||||
[butteraugli]: https://github.com/google/butteraugli
|
||||
[readfile]: https://nodejs.org/api/fs.html#fs_fspromises_readfile_path_options
|
||||
|
49
libsquoosh/lib/chunk-plugin.js
Normal file
49
libsquoosh/lib/chunk-plugin.js
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { promises as fs } from 'fs';
|
||||
import { basename } from 'path';
|
||||
|
||||
const defaultOpts = {
|
||||
prefix: 'chunk-url',
|
||||
};
|
||||
|
||||
export default function chunkPlugin(opts) {
|
||||
opts = { ...defaultOpts, ...opts };
|
||||
|
||||
const prefix = opts.prefix + ':';
|
||||
return {
|
||||
name: 'chunk-plugin',
|
||||
async resolveId(id, importer) {
|
||||
if (!id.startsWith(prefix)) return;
|
||||
const realId = id.slice(prefix.length);
|
||||
const resolveResult = await this.resolve(realId, importer);
|
||||
|
||||
if (!resolveResult) {
|
||||
throw Error(`Cannot find ${realId}`);
|
||||
}
|
||||
return prefix + resolveResult.id;
|
||||
},
|
||||
async load(id) {
|
||||
if (!id.startsWith(prefix)) return;
|
||||
const realId = id.slice(prefix.length);
|
||||
const source = await fs.readFile(realId);
|
||||
this.addWatchFile(realId);
|
||||
|
||||
return `export default import.meta.ROLLUP_FILE_URL_${this.emitFile({
|
||||
type: 'chunk',
|
||||
source,
|
||||
id: realId,
|
||||
})}`;
|
||||
},
|
||||
};
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import { ImagePool } from './build/index.js';
|
||||
|
||||
console.log("Starting");
|
||||
const imagePool = new ImagePool();
|
||||
// const imagePath = '/Users/surma/Downloads/happy_dog.png';
|
||||
const imagePath = './squoosh.png';
|
||||
console.log("INgesting");
|
||||
const image = imagePool.ingestImage(imagePath);
|
||||
console.log("Decoding");
|
||||
await image.decoded;
|
||||
const encodeOptions = {
|
||||
mozjpeg: 'auto',
|
||||
};
|
||||
console.log("Encoding");
|
||||
await image.encode(encodeOptions);
|
||||
console.log("Closing");
|
||||
await imagePool.close();
|
||||
console.log("Done");
|
2100
libsquoosh/package-lock.json
generated
2100
libsquoosh/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@squoosh/lib",
|
||||
"version": "0.2.3",
|
||||
"version": "0.4.0",
|
||||
"description": "A Node library for Squoosh",
|
||||
"public": true,
|
||||
"main": "./build/index.js",
|
||||
@ -12,8 +12,17 @@
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Google Chrome Developers <chromium-dev@google.com>",
|
||||
"homepage": "https://github.com/GoogleChromeLabs/squoosh",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/GoogleChromeLabs/squoosh.git"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": " ^12.5.0 || ^14.0.0 || ^16.0.0 "
|
||||
},
|
||||
"dependencies": {
|
||||
"wasm-feature-detect": "^1.2.11",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -2,6 +2,7 @@ import resolve from '@rollup/plugin-node-resolve';
|
||||
import cjs from '@rollup/plugin-commonjs';
|
||||
import simpleTS from './lib/simple-ts';
|
||||
import asset from './lib/asset-plugin.js';
|
||||
import chunk from './lib/chunk-plugin.js';
|
||||
import json from './lib/json-plugin.js';
|
||||
import autojson from './lib/autojson-plugin.js';
|
||||
import { getBabelOutputPlugin } from '@rollup/plugin-babel';
|
||||
@ -9,7 +10,7 @@ import { builtinModules } from 'module';
|
||||
|
||||
/** @type {import('rollup').RollupOptions} */
|
||||
export default {
|
||||
input: 'src/index.js',
|
||||
input: 'src/index.ts',
|
||||
output: {
|
||||
dir: 'build',
|
||||
format: 'cjs',
|
||||
@ -18,6 +19,7 @@ export default {
|
||||
plugins: [
|
||||
resolve(),
|
||||
cjs(),
|
||||
chunk(),
|
||||
asset(),
|
||||
autojson(),
|
||||
json(),
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 50 KiB |
@ -2,6 +2,39 @@ import { instantiateEmscriptenWasm } from './emscripten-utils.js';
|
||||
|
||||
import visdif from '../../codecs/visdif/visdif.js';
|
||||
import visdifWasm from 'asset-url:../../codecs/visdif/visdif.wasm';
|
||||
import type ImageData from './image_data';
|
||||
|
||||
interface VisDiff {
|
||||
distance: (data: Uint8ClampedArray) => number;
|
||||
delete: () => void;
|
||||
}
|
||||
|
||||
interface VisdiffConstructor {
|
||||
new (data: Uint8ClampedArray, width: number, height: number): VisDiff;
|
||||
}
|
||||
|
||||
interface VisDiffModule extends EmscriptenWasm.Module {
|
||||
VisDiff: VisdiffConstructor;
|
||||
}
|
||||
|
||||
type VisDiffModuleFactory = EmscriptenWasm.ModuleFactory<VisDiffModule>;
|
||||
|
||||
interface BinarySearchParams {
|
||||
min?: number;
|
||||
max?: number;
|
||||
epsilon?: number;
|
||||
maxRounds?: number;
|
||||
}
|
||||
|
||||
interface AutoOptimizeParams extends BinarySearchParams {
|
||||
butteraugliDistanceGoal?: number;
|
||||
}
|
||||
|
||||
interface AutoOptimizeResult {
|
||||
bitmap: ImageData;
|
||||
binary: Uint8Array;
|
||||
quality: number;
|
||||
}
|
||||
|
||||
// `measure` is a (async) function that takes exactly one numeric parameter and
|
||||
// returns a value. The function is assumed to be monotonic (an increase in `parameter`
|
||||
@ -9,9 +42,9 @@ import visdifWasm from 'asset-url:../../codecs/visdif/visdif.wasm';
|
||||
// to find `parameter` such that `measure` returns `measureGoal`, within an error
|
||||
// of `epsilon`. It will use at most `maxRounds` attempts.
|
||||
export async function binarySearch(
|
||||
measureGoal,
|
||||
measure,
|
||||
{ min = 0, max = 100, epsilon = 0.1, maxRounds = 6 } = {},
|
||||
measureGoal: number,
|
||||
measure: (val: number) => Promise<number>,
|
||||
{ min = 0, max = 100, epsilon = 0.1, maxRounds = 6 }: BinarySearchParams = {},
|
||||
) {
|
||||
let parameter = (max - min) / 2 + min;
|
||||
let delta = (max - min) / 4;
|
||||
@ -33,12 +66,21 @@ export async function binarySearch(
|
||||
}
|
||||
|
||||
export async function autoOptimize(
|
||||
bitmapIn,
|
||||
encode,
|
||||
decode,
|
||||
{ butteraugliDistanceGoal = 1.4, ...otherOpts } = {},
|
||||
) {
|
||||
const { VisDiff } = await instantiateEmscriptenWasm(visdif, visdifWasm);
|
||||
bitmapIn: ImageData,
|
||||
encode: (
|
||||
bitmap: ImageData,
|
||||
quality: number,
|
||||
) => Promise<Uint8Array> | Uint8Array,
|
||||
decode: (binary: Uint8Array) => Promise<ImageData> | ImageData,
|
||||
{
|
||||
butteraugliDistanceGoal = 1.4,
|
||||
...binarySearchParams
|
||||
}: AutoOptimizeParams = {},
|
||||
): Promise<AutoOptimizeResult> {
|
||||
const { VisDiff } = await instantiateEmscriptenWasm(
|
||||
visdif as VisDiffModuleFactory,
|
||||
visdifWasm,
|
||||
);
|
||||
|
||||
const comparator = new VisDiff(
|
||||
bitmapIn.data,
|
||||
@ -46,8 +88,11 @@ export async function autoOptimize(
|
||||
bitmapIn.height,
|
||||
);
|
||||
|
||||
let bitmapOut;
|
||||
let binaryOut;
|
||||
// We're able to do non null assertion because
|
||||
// we know that binarySearch will set these values
|
||||
let bitmapOut!: ImageData;
|
||||
let binaryOut!: Uint8Array;
|
||||
|
||||
// Increasing quality means _decrease_ in Butteraugli distance.
|
||||
// `binarySearch` assumes that increasing `parameter` will
|
||||
// increase the metric value. So multipliy Butteraugli values by -1.
|
||||
@ -58,7 +103,7 @@ export async function autoOptimize(
|
||||
bitmapOut = await decode(binaryOut);
|
||||
return -1 * comparator.distance(bitmapOut.data);
|
||||
},
|
||||
otherOpts,
|
||||
binarySearchParams,
|
||||
);
|
||||
comparator.delete();
|
||||
|
@ -1,5 +1,19 @@
|
||||
import { promises as fsp } from 'fs';
|
||||
import { instantiateEmscriptenWasm, pathify } from './emscripten-utils.js';
|
||||
import { threads } from 'wasm-feature-detect';
|
||||
import { cpus } from 'os';
|
||||
|
||||
// We use `navigator.hardwareConcurrency` for Emscripten’s pthread pool size.
|
||||
// This is the only workaround I can get working without crying.
|
||||
(globalThis as any).navigator = {
|
||||
hardwareConcurrency: cpus().length,
|
||||
};
|
||||
|
||||
interface DecodeModule extends EmscriptenWasm.Module {
|
||||
decode: (data: Uint8Array) => ImageData;
|
||||
}
|
||||
|
||||
type DecodeModuleFactory = EmscriptenWasm.ModuleFactory<DecodeModule>;
|
||||
|
||||
interface RotateModuleInstance {
|
||||
exports: {
|
||||
@ -25,7 +39,7 @@ interface ResizeInstantiateOptions {
|
||||
|
||||
declare global {
|
||||
// Needed for being able to use ImageData as type in codec types
|
||||
type ImageData = typeof import('./image_data.js');
|
||||
type ImageData = import('./image_data.js').default;
|
||||
// Needed for being able to assign to `globalThis.ImageData`
|
||||
var ImageData: ImageData['constructor'];
|
||||
}
|
||||
@ -33,30 +47,38 @@ declare global {
|
||||
import type { QuantizerModule } from '../../codecs/imagequant/imagequant.js';
|
||||
|
||||
// MozJPEG
|
||||
import type { MozJPEGModule as MozJPEGEncodeModule } from '../../codecs/mozjpeg/enc/mozjpeg_enc';
|
||||
import mozEnc from '../../codecs/mozjpeg/enc/mozjpeg_node_enc.js';
|
||||
import mozEncWasm from 'asset-url:../../codecs/mozjpeg/enc/mozjpeg_node_enc.wasm';
|
||||
import mozDec from '../../codecs/mozjpeg/dec/mozjpeg_node_dec.js';
|
||||
import mozDecWasm from 'asset-url:../../codecs/mozjpeg/dec/mozjpeg_node_dec.wasm';
|
||||
|
||||
// WebP
|
||||
import type { WebPModule as WebPEncodeModule } from '../../codecs/webp/enc/webp_enc';
|
||||
import webpEnc from '../../codecs/webp/enc/webp_node_enc.js';
|
||||
import webpEncWasm from 'asset-url:../../codecs/webp/enc/webp_node_enc.wasm';
|
||||
import webpDec from '../../codecs/webp/dec/webp_node_dec.js';
|
||||
import webpDecWasm from 'asset-url:../../codecs/webp/dec/webp_node_dec.wasm';
|
||||
|
||||
// AVIF
|
||||
import type { AVIFModule as AVIFEncodeModule } from '../../codecs/avif/enc/avif_enc';
|
||||
import avifEnc from '../../codecs/avif/enc/avif_node_enc.js';
|
||||
import avifEncWasm from 'asset-url:../../codecs/avif/enc/avif_node_enc.wasm';
|
||||
import avifEncMt from '../../codecs/avif/enc/avif_node_enc_mt.js';
|
||||
import avifEncMtWorker from 'chunk-url:../../codecs/avif/enc/avif_node_enc_mt.worker.js';
|
||||
import avifEncMtWasm from 'asset-url:../../codecs/avif/enc/avif_node_enc_mt.wasm';
|
||||
import avifDec from '../../codecs/avif/dec/avif_node_dec.js';
|
||||
import avifDecWasm from 'asset-url:../../codecs/avif/dec/avif_node_dec.wasm';
|
||||
|
||||
// JXL
|
||||
import type { JXLModule as JXLEncodeModule } from '../../codecs/jxl/enc/jxl_enc';
|
||||
import jxlEnc from '../../codecs/jxl/enc/jxl_node_enc.js';
|
||||
import jxlEncWasm from 'asset-url:../../codecs/jxl/enc/jxl_node_enc.wasm';
|
||||
import jxlDec from '../../codecs/jxl/dec/jxl_node_dec.js';
|
||||
import jxlDecWasm from 'asset-url:../../codecs/jxl/dec/jxl_node_dec.wasm';
|
||||
|
||||
// WP2
|
||||
import type { WP2Module as WP2EncodeModule } from '../../codecs/wp2/enc/wp2_enc';
|
||||
import wp2Enc from '../../codecs/wp2/enc/wp2_node_enc.js';
|
||||
import wp2EncWasm from 'asset-url:../../codecs/wp2/enc/wp2_node_enc.wasm';
|
||||
import wp2Dec from '../../codecs/wp2/dec/wp2_node_dec.js';
|
||||
@ -246,15 +268,20 @@ export const preprocessors = {
|
||||
numRotations: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
} as const;
|
||||
|
||||
export const codecs = {
|
||||
mozjpeg: {
|
||||
name: 'MozJPEG',
|
||||
extension: 'jpg',
|
||||
detectors: [/^\xFF\xD8\xFF/],
|
||||
dec: () => instantiateEmscriptenWasm(mozDec, mozDecWasm),
|
||||
enc: () => instantiateEmscriptenWasm(mozEnc, mozEncWasm),
|
||||
dec: () =>
|
||||
instantiateEmscriptenWasm(mozDec as DecodeModuleFactory, mozDecWasm),
|
||||
enc: () =>
|
||||
instantiateEmscriptenWasm(
|
||||
mozEnc as EmscriptenWasm.ModuleFactory<MozJPEGEncodeModule>,
|
||||
mozEncWasm,
|
||||
),
|
||||
defaultEncoderOptions: {
|
||||
quality: 75,
|
||||
baseline: false,
|
||||
@ -282,9 +309,14 @@ export const codecs = {
|
||||
webp: {
|
||||
name: 'WebP',
|
||||
extension: 'webp',
|
||||
detectors: [/^RIFF....WEBPVP8[LX ]/],
|
||||
dec: () => instantiateEmscriptenWasm(webpDec, webpDecWasm),
|
||||
enc: () => instantiateEmscriptenWasm(webpEnc, webpEncWasm),
|
||||
detectors: [/^RIFF....WEBPVP8[LX ]/s],
|
||||
dec: () =>
|
||||
instantiateEmscriptenWasm(webpDec as DecodeModuleFactory, webpDecWasm),
|
||||
enc: () =>
|
||||
instantiateEmscriptenWasm(
|
||||
webpEnc as EmscriptenWasm.ModuleFactory<WebPEncodeModule>,
|
||||
webpEncWasm,
|
||||
),
|
||||
defaultEncoderOptions: {
|
||||
quality: 75,
|
||||
target_size: 0,
|
||||
@ -324,8 +356,21 @@ export const codecs = {
|
||||
name: 'AVIF',
|
||||
extension: 'avif',
|
||||
detectors: [/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/],
|
||||
dec: () => instantiateEmscriptenWasm(avifDec, avifDecWasm),
|
||||
enc: () => instantiateEmscriptenWasm(avifEnc, avifEncWasm),
|
||||
dec: () =>
|
||||
instantiateEmscriptenWasm(avifDec as DecodeModuleFactory, avifDecWasm),
|
||||
enc: async () => {
|
||||
if (await threads()) {
|
||||
return instantiateEmscriptenWasm(
|
||||
avifEncMt as EmscriptenWasm.ModuleFactory<AVIFEncodeModule>,
|
||||
avifEncMtWasm,
|
||||
avifEncMtWorker,
|
||||
);
|
||||
}
|
||||
return instantiateEmscriptenWasm(
|
||||
avifEnc as EmscriptenWasm.ModuleFactory<AVIFEncodeModule>,
|
||||
avifEncWasm,
|
||||
);
|
||||
},
|
||||
defaultEncoderOptions: {
|
||||
cqLevel: 33,
|
||||
cqAlphaLevel: -1,
|
||||
@ -348,8 +393,13 @@ export const codecs = {
|
||||
name: 'JPEG-XL',
|
||||
extension: 'jxl',
|
||||
detectors: [/^\xff\x0a/],
|
||||
dec: () => instantiateEmscriptenWasm(jxlDec, jxlDecWasm),
|
||||
enc: () => instantiateEmscriptenWasm(jxlEnc, jxlEncWasm),
|
||||
dec: () =>
|
||||
instantiateEmscriptenWasm(jxlDec as DecodeModuleFactory, jxlDecWasm),
|
||||
enc: () =>
|
||||
instantiateEmscriptenWasm(
|
||||
jxlEnc as EmscriptenWasm.ModuleFactory<JXLEncodeModule>,
|
||||
jxlEncWasm,
|
||||
),
|
||||
defaultEncoderOptions: {
|
||||
speed: 4,
|
||||
quality: 75,
|
||||
@ -369,8 +419,13 @@ export const codecs = {
|
||||
name: 'WebP2',
|
||||
extension: 'wp2',
|
||||
detectors: [/^\xF4\xFF\x6F/],
|
||||
dec: () => instantiateEmscriptenWasm(wp2Dec, wp2DecWasm),
|
||||
enc: () => instantiateEmscriptenWasm(wp2Enc, wp2EncWasm),
|
||||
dec: () =>
|
||||
instantiateEmscriptenWasm(wp2Dec as DecodeModuleFactory, wp2DecWasm),
|
||||
enc: () =>
|
||||
instantiateEmscriptenWasm(
|
||||
wp2Enc as EmscriptenWasm.ModuleFactory<WP2EncodeModule>,
|
||||
wp2EncWasm,
|
||||
),
|
||||
defaultEncoderOptions: {
|
||||
quality: 75,
|
||||
alpha_quality: 75,
|
||||
@ -401,7 +456,7 @@ export const codecs = {
|
||||
await oxipngPromise;
|
||||
return {
|
||||
encode: (
|
||||
buffer: Uint8Array,
|
||||
buffer: Uint8ClampedArray | ArrayBuffer,
|
||||
width: number,
|
||||
height: number,
|
||||
opts: { level: number },
|
||||
@ -424,4 +479,4 @@ export const codecs = {
|
||||
max: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
} as const;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { fileURLToPath } from 'url';
|
||||
import { fileURLToPath, URL } from 'url';
|
||||
|
||||
export function pathify(path: string): string {
|
||||
if (path.startsWith('file://')) {
|
||||
@ -10,10 +10,17 @@ export function pathify(path: string): string {
|
||||
export function instantiateEmscriptenWasm<T extends EmscriptenWasm.Module>(
|
||||
factory: EmscriptenWasm.ModuleFactory<T>,
|
||||
path: string,
|
||||
workerJS: string = '',
|
||||
): Promise<T> {
|
||||
return factory({
|
||||
locateFile() {
|
||||
return pathify(path);
|
||||
locateFile(requestPath) {
|
||||
// The glue code generated by emscripten uses the original
|
||||
// file names of the worker file and the wasm binary.
|
||||
// These will have changed in the bundling process and
|
||||
// we need to inject them here.
|
||||
if (requestPath.endsWith('.wasm')) return pathify(path);
|
||||
if (requestPath.endsWith('.worker.js')) return new URL(workerJS).pathname;
|
||||
return requestPath;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -5,10 +5,18 @@ import { promises as fsp } from 'fs';
|
||||
import { codecs as encoders, preprocessors } from './codecs.js';
|
||||
import WorkerPool from './worker_pool.js';
|
||||
import { autoOptimize } from './auto-optimizer.js';
|
||||
import type ImageData from './image_data';
|
||||
|
||||
export { ImagePool, encoders, preprocessors };
|
||||
type EncoderKey = keyof typeof encoders;
|
||||
type PreprocessorKey = keyof typeof preprocessors;
|
||||
type FileLike = Buffer | ArrayBuffer | string | ArrayBufferView;
|
||||
|
||||
async function decodeFile({ file }) {
|
||||
async function decodeFile({
|
||||
file,
|
||||
}: {
|
||||
file: FileLike;
|
||||
}): Promise<{ bitmap: ImageData; size: number }> {
|
||||
let buffer;
|
||||
if (ArrayBuffer.isView(file)) {
|
||||
buffer = Buffer.from(file.buffer);
|
||||
@ -16,8 +24,9 @@ async function decodeFile({ file }) {
|
||||
} else if (file instanceof ArrayBuffer) {
|
||||
buffer = Buffer.from(file);
|
||||
file = 'Binary blob';
|
||||
} else if (file instanceof Buffer) {
|
||||
buffer = file;
|
||||
} else if ((file as unknown) instanceof Buffer) {
|
||||
// TODO: Check why we need type assertions here.
|
||||
buffer = (file as unknown) as Buffer;
|
||||
file = 'Binary blob';
|
||||
} else if (typeof file === 'string') {
|
||||
buffer = await fsp.readFile(file);
|
||||
@ -28,23 +37,33 @@ async function decodeFile({ file }) {
|
||||
const firstChunkString = Array.from(firstChunk)
|
||||
.map((v) => String.fromCodePoint(v))
|
||||
.join('');
|
||||
const key = Object.entries(encoders).find(([name, { detectors }]) =>
|
||||
const key = Object.entries(encoders).find(([_name, { detectors }]) =>
|
||||
detectors.some((detector) => detector.exec(firstChunkString)),
|
||||
)?.[0];
|
||||
)?.[0] as EncoderKey | undefined;
|
||||
if (!key) {
|
||||
throw Error(`${file} has an unsupported format`);
|
||||
}
|
||||
const rgba = (await encoders[key].dec()).decode(new Uint8Array(buffer));
|
||||
const encoder = encoders[key];
|
||||
const mod = await encoder.dec();
|
||||
const rgba = mod.decode(new Uint8Array(buffer));
|
||||
return {
|
||||
bitmap: rgba,
|
||||
size: buffer.length,
|
||||
};
|
||||
}
|
||||
|
||||
async function preprocessImage({ preprocessorName, options, image }) {
|
||||
async function preprocessImage({
|
||||
preprocessorName,
|
||||
options,
|
||||
image,
|
||||
}: {
|
||||
preprocessorName: PreprocessorKey;
|
||||
options: any;
|
||||
image: { bitmap: ImageData };
|
||||
}) {
|
||||
const preprocessor = await preprocessors[preprocessorName].instantiate();
|
||||
image.bitmap = await preprocessor(
|
||||
image.bitmap.data,
|
||||
Uint8Array.from(image.bitmap.data),
|
||||
image.bitmap.width,
|
||||
image.bitmap.height,
|
||||
options,
|
||||
@ -58,26 +77,39 @@ async function encodeImage({
|
||||
encConfig,
|
||||
optimizerButteraugliTarget,
|
||||
maxOptimizerRounds,
|
||||
}: {
|
||||
bitmap: ImageData;
|
||||
encName: EncoderKey;
|
||||
encConfig: any;
|
||||
optimizerButteraugliTarget: number;
|
||||
maxOptimizerRounds: number;
|
||||
}) {
|
||||
let binary;
|
||||
let binary: Uint8Array;
|
||||
let optionsUsed = encConfig;
|
||||
const encoder = await encoders[encName].enc();
|
||||
if (encConfig === 'auto') {
|
||||
const optionToOptimize = encoders[encName].autoOptimize.option;
|
||||
const decoder = await encoders[encName].dec();
|
||||
const encode = (bitmapIn, quality) =>
|
||||
const encode = (bitmapIn: ImageData, quality: number) =>
|
||||
encoder.encode(
|
||||
bitmapIn.data,
|
||||
bitmapIn.width,
|
||||
bitmapIn.height,
|
||||
Object.assign({}, encoders[encName].defaultEncoderOptions, {
|
||||
Object.assign({}, encoders[encName].defaultEncoderOptions as any, {
|
||||
[optionToOptimize]: quality,
|
||||
}),
|
||||
);
|
||||
const decode = (binary) => decoder.decode(binary);
|
||||
const decode = (binary: Uint8Array) => decoder.decode(binary);
|
||||
const nonNullEncode = (bitmap: ImageData, quality: number): Uint8Array => {
|
||||
const result = encode(bitmap, quality);
|
||||
if (!result) {
|
||||
throw new Error('There was an error while encoding');
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const { binary: optimizedBinary, quality } = await autoOptimize(
|
||||
bitmapIn,
|
||||
encode,
|
||||
nonNullEncode,
|
||||
decode,
|
||||
{
|
||||
min: encoders[encName].autoOptimize.min,
|
||||
@ -92,12 +124,18 @@ async function encodeImage({
|
||||
[optionToOptimize]: Math.round(quality * 10000) / 10000,
|
||||
};
|
||||
} else {
|
||||
binary = encoder.encode(
|
||||
const result = encoder.encode(
|
||||
bitmapIn.data.buffer,
|
||||
bitmapIn.width,
|
||||
bitmapIn.height,
|
||||
encConfig,
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
throw new Error('There was an error while encoding');
|
||||
}
|
||||
|
||||
binary = result;
|
||||
}
|
||||
return {
|
||||
optionsUsed,
|
||||
@ -107,10 +145,15 @@ async function encodeImage({
|
||||
};
|
||||
}
|
||||
|
||||
// both decoding and encoding go through the worker pool
|
||||
function handleJob(params) {
|
||||
const { operation } = params;
|
||||
switch (operation) {
|
||||
type EncodeParams = { operation: 'encode' } & Parameters<typeof encodeImage>[0];
|
||||
type DecodeParams = { operation: 'decode' } & Parameters<typeof decodeFile>[0];
|
||||
type PreprocessParams = { operation: 'preprocess' } & Parameters<
|
||||
typeof preprocessImage
|
||||
>[0];
|
||||
type JobMessage = EncodeParams | DecodeParams | PreprocessParams;
|
||||
|
||||
function handleJob(params: JobMessage) {
|
||||
switch (params.operation) {
|
||||
case 'encode':
|
||||
return encodeImage(params);
|
||||
case 'decode':
|
||||
@ -118,7 +161,7 @@ function handleJob(params) {
|
||||
case 'preprocess':
|
||||
return preprocessImage(params);
|
||||
default:
|
||||
throw Error(`Invalid job "${operation}"`);
|
||||
throw Error(`Invalid job "${(params as any).operation}"`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -126,7 +169,12 @@ function handleJob(params) {
|
||||
* Represents an ingested image.
|
||||
*/
|
||||
class Image {
|
||||
constructor(workerPool, file) {
|
||||
public file: FileLike;
|
||||
public workerPool: WorkerPool<JobMessage, any>;
|
||||
public decoded: Promise<{ bitmap: ImageData }>;
|
||||
public encodedWith: { [key: string]: any };
|
||||
|
||||
constructor(workerPool: WorkerPool<JobMessage, any>, file: FileLike) {
|
||||
this.file = file;
|
||||
this.workerPool = workerPool;
|
||||
this.decoded = workerPool.dispatchJob({ operation: 'decode', file });
|
||||
@ -143,14 +191,15 @@ class Image {
|
||||
if (!Object.keys(preprocessors).includes(name)) {
|
||||
throw Error(`Invalid preprocessor "${name}"`);
|
||||
}
|
||||
const preprocessorName = name as PreprocessorKey;
|
||||
const preprocessorOptions = Object.assign(
|
||||
{},
|
||||
preprocessors[name].defaultOptions,
|
||||
preprocessors[preprocessorName].defaultOptions,
|
||||
options,
|
||||
);
|
||||
this.decoded = this.workerPool.dispatchJob({
|
||||
operation: 'preprocess',
|
||||
preprocessorName: name,
|
||||
preprocessorName,
|
||||
image: await this.decoded,
|
||||
options: preprocessorOptions,
|
||||
});
|
||||
@ -161,14 +210,22 @@ class Image {
|
||||
/**
|
||||
* Define one or several encoders to use on the image.
|
||||
* @param {object} encodeOptions - An object with encoders to use, and their settings.
|
||||
* @returns {Promise<undefined>} - A promise that resolves when the image has been encoded with all the specified encoders.
|
||||
* @returns {Promise<void>} - A promise that resolves when the image has been encoded with all the specified encoders.
|
||||
*/
|
||||
async encode(encodeOptions = {}) {
|
||||
async encode(
|
||||
encodeOptions: {
|
||||
optimizerButteraugliTarget?: number;
|
||||
maxOptimizerRounds?: number;
|
||||
} & {
|
||||
[key in EncoderKey]?: any; // any is okay for now
|
||||
} = {},
|
||||
): Promise<void> {
|
||||
const { bitmap } = await this.decoded;
|
||||
for (const [encName, options] of Object.entries(encodeOptions)) {
|
||||
if (!Object.keys(encoders).includes(encName)) {
|
||||
for (const [name, options] of Object.entries(encodeOptions)) {
|
||||
if (!Object.keys(encoders).includes(name)) {
|
||||
continue;
|
||||
}
|
||||
const encName = name as EncoderKey;
|
||||
const encRef = encoders[encName];
|
||||
const encConfig =
|
||||
typeof options === 'string'
|
||||
@ -193,28 +250,30 @@ class Image {
|
||||
* A pool where images can be ingested and squooshed.
|
||||
*/
|
||||
class ImagePool {
|
||||
public workerPool: WorkerPool<JobMessage, any>;
|
||||
|
||||
/**
|
||||
* Create a new pool.
|
||||
* @param {number} [threads] - Number of concurrent image processes to run in the pool. Defaults to the number of CPU cores in the system.
|
||||
*/
|
||||
constructor(threads) {
|
||||
constructor(threads: number) {
|
||||
this.workerPool = new WorkerPool(threads || cpus().length, __filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ingest an image into the image pool.
|
||||
* @param {string | Buffer | URL | object} image - The image or path to the image that should be ingested and decoded.
|
||||
* @param {FileLike} image - The image or path to the image that should be ingested and decoded.
|
||||
* @returns {Image} - A custom class reference to the decoded image.
|
||||
*/
|
||||
ingestImage(image) {
|
||||
ingestImage(image: FileLike): Image {
|
||||
return new Image(this.workerPool, image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the underlying image processing pipeline. The already processed images will still be there, but no new processing can start.
|
||||
* @returns {Promise<undefined>} - A promise that resolves when the underlying pipeline has closed.
|
||||
* @returns {Promise<void>} - A promise that resolves when the underlying pipeline has closed.
|
||||
*/
|
||||
async close() {
|
||||
async close(): Promise<void> {
|
||||
await this.workerPool.join();
|
||||
}
|
||||
}
|
5
libsquoosh/src/missing-types.d.ts
vendored
5
libsquoosh/src/missing-types.d.ts
vendored
@ -23,6 +23,11 @@ declare module 'asset-url:../../codecs/resize/pkg/squoosh_resize_bg.wasm' {
|
||||
export default value;
|
||||
}
|
||||
|
||||
declare module 'chunk-url:../../codecs/avif/enc/avif_node_enc_mt.worker.js' {
|
||||
const value: string;
|
||||
export default value;
|
||||
}
|
||||
|
||||
// These don't exist in NodeJS types so we're not able to use them but they are referenced in some emscripten and codec types
|
||||
// Thus, we need to explicitly assign them to be `never`
|
||||
// We're also not able to use the APIs that use these types
|
||||
|
@ -7,26 +7,19 @@ function uuid() {
|
||||
).join('');
|
||||
}
|
||||
|
||||
function jobPromise(worker, msg) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = uuid();
|
||||
worker.postMessage({ msg, id });
|
||||
worker.on('message', function f({ error, result, id: rid }) {
|
||||
if (rid !== id) {
|
||||
return;
|
||||
}
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
worker.off('message', f);
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
interface Job<I> {
|
||||
msg: I;
|
||||
resolve: Function;
|
||||
reject: Function;
|
||||
}
|
||||
|
||||
export default class WorkerPool {
|
||||
constructor(numWorkers, workerFile) {
|
||||
export default class WorkerPool<I, O> {
|
||||
public numWorkers: number;
|
||||
public jobQueue: TransformStream<Job<I>, Job<I>>;
|
||||
public workerQueue: TransformStream<Worker, Worker>;
|
||||
public done: Promise<void>;
|
||||
|
||||
constructor(numWorkers: number, workerFile: string) {
|
||||
this.numWorkers = numWorkers;
|
||||
this.jobQueue = new TransformStream();
|
||||
this.workerQueue = new TransformStream();
|
||||
@ -48,9 +41,14 @@ export default class WorkerPool {
|
||||
await this._terminateAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
throw new Error('Reader did not return any value');
|
||||
}
|
||||
|
||||
const { msg, resolve, reject } = value;
|
||||
const worker = await this._nextWorker();
|
||||
jobPromise(worker, msg)
|
||||
this.jobPromise(worker, msg)
|
||||
.then((result) => resolve(result))
|
||||
.catch((reason) => reject(reason))
|
||||
.finally(() => {
|
||||
@ -64,8 +62,12 @@ export default class WorkerPool {
|
||||
|
||||
async _nextWorker() {
|
||||
const reader = this.workerQueue.readable.getReader();
|
||||
const { value, done } = await reader.read();
|
||||
const { value } = await reader.read();
|
||||
reader.releaseLock();
|
||||
if (!value) {
|
||||
throw new Error('No worker left');
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -82,7 +84,7 @@ export default class WorkerPool {
|
||||
await this.done;
|
||||
}
|
||||
|
||||
dispatchJob(msg) {
|
||||
dispatchJob(msg: I): Promise<O> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const writer = this.jobQueue.writable.getWriter();
|
||||
writer.write({ msg, resolve, reject });
|
||||
@ -90,14 +92,32 @@ export default class WorkerPool {
|
||||
});
|
||||
}
|
||||
|
||||
static useThisThreadAsWorker(cb) {
|
||||
parentPort.on('message', async (data) => {
|
||||
private jobPromise(worker: Worker, msg: I) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = uuid();
|
||||
worker.postMessage({ msg, id });
|
||||
worker.on('message', function f({ error, result, id: rid }) {
|
||||
if (rid !== id) {
|
||||
return;
|
||||
}
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
worker.off('message', f);
|
||||
resolve(result);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static useThisThreadAsWorker<I, O>(cb: (msg: I) => O) {
|
||||
parentPort!.on('message', async (data) => {
|
||||
const { msg, id } = data;
|
||||
try {
|
||||
const result = await cb(msg);
|
||||
parentPort.postMessage({ result, id });
|
||||
parentPort!.postMessage({ result, id });
|
||||
} catch (e) {
|
||||
parentPort.postMessage({ error: e.message, id });
|
||||
parentPort!.postMessage({ error: e.message, id });
|
||||
}
|
||||
});
|
||||
}
|
@ -6,9 +6,9 @@
|
||||
"scripts": {
|
||||
"build": "rollup -c && node lib/move-output.js",
|
||||
"debug": "node --inspect-brk node_modules/.bin/rollup -c",
|
||||
"dev": "run-p watch serve",
|
||||
"dev": "DEV_PORT=\"${DEV_PORT:=5000}\" run-p watch serve",
|
||||
"watch": "rollup -cw",
|
||||
"serve": "serve --config ../../../serve.json .tmp/build/static"
|
||||
"serve": "serve --listen=$DEV_PORT --config ../../../serve.json .tmp/build/static"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^17.0.0",
|
||||
|
@ -112,18 +112,23 @@ class RangeInputElement extends HTMLElement {
|
||||
this.dispatchEvent(retargetted);
|
||||
};
|
||||
|
||||
private _formatDisplayValue(value: number): string {
|
||||
const labelPrecision =
|
||||
Number(this.labelPrecision) || getPrescision(this.step) || 0;
|
||||
if (labelPrecision) {
|
||||
return value.toFixed(labelPrecision);
|
||||
}
|
||||
return Math.round(value).toString();
|
||||
}
|
||||
|
||||
private _update = () => {
|
||||
// Not connected?
|
||||
if (!this._valueDisplay) return;
|
||||
const value = Number(this.value) || 0;
|
||||
const min = Number(this.min) || 0;
|
||||
const max = Number(this.max) || 100;
|
||||
const labelPrecision =
|
||||
Number(this.labelPrecision) || getPrescision(this.step) || 0;
|
||||
const percent = (100 * (value - min)) / (max - min);
|
||||
const displayValue = labelPrecision
|
||||
? value.toFixed(labelPrecision)
|
||||
: Math.round(value).toString();
|
||||
const displayValue = this._formatDisplayValue(value);
|
||||
|
||||
this._valueDisplay!.textContent = displayValue;
|
||||
this.style.setProperty('--value-percent', percent + '%');
|
||||
|
@ -6,10 +6,13 @@ import './custom-els/RangeInput';
|
||||
import { linkRef } from 'shared/prerendered-app/util';
|
||||
|
||||
interface Props extends preact.JSX.HTMLAttributes {}
|
||||
interface State {}
|
||||
interface State {
|
||||
textFocused: boolean;
|
||||
}
|
||||
|
||||
export default class Range extends Component<Props, State> {
|
||||
rangeWc?: RangeInputElement;
|
||||
inputEl?: HTMLInputElement;
|
||||
|
||||
private onTextInput = (event: Event) => {
|
||||
const input = event.target as HTMLInputElement;
|
||||
@ -23,10 +26,19 @@ export default class Range extends Component<Props, State> {
|
||||
);
|
||||
};
|
||||
|
||||
render(props: Props) {
|
||||
private onTextFocus = () => {
|
||||
this.setState({ textFocused: true });
|
||||
};
|
||||
|
||||
private onTextBlur = () => {
|
||||
this.setState({ textFocused: false });
|
||||
};
|
||||
|
||||
render(props: Props, state: State) {
|
||||
const { children, ...otherProps } = props;
|
||||
|
||||
const { value, min, max, step } = props;
|
||||
const textValue = state.textFocused ? this.inputEl!.value : value;
|
||||
|
||||
return (
|
||||
<label class={style.range}>
|
||||
@ -41,13 +53,16 @@ export default class Range extends Component<Props, State> {
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
ref={linkRef(this, 'inputEl')}
|
||||
type="number"
|
||||
class={style.textInput}
|
||||
value={value}
|
||||
value={textValue}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
onInput={this.onTextInput}
|
||||
onFocus={this.onTextFocus}
|
||||
onBlur={this.onTextBlur}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
.options-scroller {
|
||||
--horizontal-padding: 15px;
|
||||
border-radius: var(--scroller-radius);
|
||||
overflow: hidden;
|
||||
|
||||
/* At smaller widths, the multi-panel handles the scrolling */
|
||||
@media (min-width: 600px) {
|
||||
|
@ -81,6 +81,7 @@ function createPoint(): SVGPoint {
|
||||
}
|
||||
|
||||
const MIN_SCALE = 0.01;
|
||||
const MAX_SCALE = 100000;
|
||||
|
||||
export default class PinchZoom extends HTMLElement {
|
||||
// The element that we'll transform.
|
||||
@ -244,6 +245,9 @@ export default class PinchZoom extends HTMLElement {
|
||||
// Avoid scaling to zero
|
||||
if (scale < MIN_SCALE) return;
|
||||
|
||||
// Avoid scaling to very large values
|
||||
if (scale > MAX_SCALE) return;
|
||||
|
||||
// Return if there's no change
|
||||
if (scale === this.scale && x === this.x && y === this.y) return;
|
||||
|
||||
@ -296,9 +300,13 @@ export default class PinchZoom extends HTMLElement {
|
||||
deltaY *= 15;
|
||||
}
|
||||
|
||||
const zoomingOut = deltaY > 0;
|
||||
|
||||
// ctrlKey is true when pinch-zooming on a trackpad.
|
||||
const divisor = ctrlKey ? 100 : 300;
|
||||
const scaleDiff = 1 - deltaY / divisor;
|
||||
// when zooming out, invert the delta and the ratio to keep zoom stable
|
||||
const ratio = 1 - (zoomingOut ? -deltaY : deltaY) / divisor;
|
||||
const scaleDiff = zoomingOut ? 1 / ratio : ratio;
|
||||
|
||||
this._applyChange({
|
||||
scaleDiff,
|
||||
|
@ -100,6 +100,9 @@ async function decodeImage(
|
||||
|
||||
try {
|
||||
if (!canDecode) {
|
||||
if (mimeType === 'image/ktx2') {
|
||||
return await workerBridge.basisDecode(signal, blob);
|
||||
}
|
||||
if (mimeType === 'image/avif') {
|
||||
return await workerBridge.avifDecode(signal, blob);
|
||||
}
|
||||
|
@ -95,11 +95,12 @@ const magicNumberMapInput = [
|
||||
[/^I I/, 'image/tiff'],
|
||||
[/^II*/, 'image/tiff'],
|
||||
[/^MM\x00*/, 'image/tiff'],
|
||||
[/^RIFF....WEBPVP8[LX ]/, 'image/webp'],
|
||||
[/^RIFF....WEBPVP8[LX ]/s, 'image/webp'],
|
||||
[/^\xF4\xFF\x6F/, 'image/webp2'],
|
||||
[/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/, 'image/avif'],
|
||||
[/^\xff\x0a/, 'image/jxl'],
|
||||
[/^\x00\x00\x00\x0cJXL \x0d\x0a\x87\x0a/, 'image/jxl'],
|
||||
[/^«KTX 20»\r\n/, 'image/ktx2'],
|
||||
] as const;
|
||||
|
||||
export type ImageMimeTypes = typeof magicNumberMapInput[number][1];
|
||||
@ -295,3 +296,16 @@ export async function abortable<T>(
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clamp a value `v` between `min` and `max`.
|
||||
*/
|
||||
export function clamp(min: number, v: number, max: number): number {
|
||||
if (v < min) {
|
||||
return min;
|
||||
}
|
||||
if (v > max) {
|
||||
return max;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
32
src/features/decoders/basis/worker/basisDecode.ts
Normal file
32
src/features/decoders/basis/worker/basisDecode.ts
Normal file
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import type { BasisModule } from 'codecs/basis/dec/basis_dec';
|
||||
import { initEmscriptenModule, blobToArrayBuffer } from 'features/worker-utils';
|
||||
|
||||
let emscriptenModule: Promise<BasisModule>;
|
||||
|
||||
export default async function decode(blob: Blob): Promise<ImageData> {
|
||||
if (!emscriptenModule) {
|
||||
const decoder = await import('codecs/basis/dec/basis_dec');
|
||||
emscriptenModule = initEmscriptenModule(decoder.default);
|
||||
}
|
||||
|
||||
const [module, data] = await Promise.all([
|
||||
emscriptenModule,
|
||||
blobToArrayBuffer(blob),
|
||||
]);
|
||||
|
||||
const result = module.decode(data);
|
||||
if (!result) throw new Error('Decoding error');
|
||||
return result;
|
||||
}
|
214
src/features/encoders/basis/client/index.tsx
Normal file
214
src/features/encoders/basis/client/index.tsx
Normal file
@ -0,0 +1,214 @@
|
||||
import { EncodeOptions, defaultOptions } from '../shared/meta';
|
||||
import type WorkerBridge from 'client/lazy-app/worker-bridge';
|
||||
import { h, Component, Fragment } from 'preact';
|
||||
import {
|
||||
inputFieldChecked,
|
||||
inputFieldValueAsNumber,
|
||||
preventDefault,
|
||||
clamp,
|
||||
} from 'client/lazy-app/util';
|
||||
import * as style from 'client/lazy-app/Compress/Options/style.css';
|
||||
import linkState from 'linkstate';
|
||||
import Range from 'client/lazy-app/Compress/Options/Range';
|
||||
import Checkbox from 'client/lazy-app/Compress/Options/Checkbox';
|
||||
import Expander from 'client/lazy-app/Compress/Options/Expander';
|
||||
import Select from 'client/lazy-app/Compress/Options/Select';
|
||||
import Revealer from 'client/lazy-app/Compress/Options/Revealer';
|
||||
|
||||
export function encode(
|
||||
signal: AbortSignal,
|
||||
workerBridge: WorkerBridge,
|
||||
imageData: ImageData,
|
||||
options: EncodeOptions,
|
||||
) {
|
||||
return workerBridge.basisEncode(signal, imageData, options);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
options: EncodeOptions;
|
||||
onChange(newOptions: EncodeOptions): void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
showAdvanced: boolean;
|
||||
}
|
||||
|
||||
export class Options extends Component<Props, State> {
|
||||
state: State = {
|
||||
showAdvanced: false,
|
||||
};
|
||||
|
||||
onChange = (event: Event) => {
|
||||
const form = (event.currentTarget as HTMLInputElement).closest(
|
||||
'form',
|
||||
) as HTMLFormElement;
|
||||
const { options } = this.props;
|
||||
|
||||
const uastc = form.mode.value === '1';
|
||||
let quality = inputFieldValueAsNumber(form.quality, options.quality);
|
||||
if (uastc) {
|
||||
quality = clamp(0, quality, 4);
|
||||
} else {
|
||||
quality = Math.floor(clamp(0, quality, 255));
|
||||
}
|
||||
|
||||
const newOptions: EncodeOptions = {
|
||||
...this.props.options,
|
||||
uastc,
|
||||
quality,
|
||||
y_flip: inputFieldChecked(form.y_flip, options.y_flip),
|
||||
perceptual: inputFieldChecked(form.perceptual, options.perceptual),
|
||||
mipmap: inputFieldChecked(form.mipmap, options.mipmap),
|
||||
srgb_mipmap: inputFieldChecked(form.srgb_mipmap, options.srgb_mipmap),
|
||||
mipmap_filter: form.mipmap_filter?.value ?? defaultOptions.mipmap_filter,
|
||||
// FIXME: We really should support range remapping
|
||||
// in the range-slider component. For now I’ll
|
||||
// shoe-horn it into the state management.
|
||||
mipmap_min_dimension:
|
||||
2 **
|
||||
inputFieldValueAsNumber(
|
||||
form.mipmap_min_dimension,
|
||||
Math.floor(Math.log2(options.mipmap_min_dimension)),
|
||||
),
|
||||
compression: inputFieldValueAsNumber(
|
||||
form.compression,
|
||||
options.compression,
|
||||
),
|
||||
};
|
||||
this.props.onChange(newOptions);
|
||||
};
|
||||
|
||||
render({ options }: Props, { showAdvanced }: State) {
|
||||
return (
|
||||
<form class={style.optionsSection} onSubmit={preventDefault}>
|
||||
<label class={style.optionTextFirst}>
|
||||
Mode:
|
||||
<Select
|
||||
name="mode"
|
||||
value={options.uastc ? '1' : '0'}
|
||||
onChange={this.onChange}
|
||||
>
|
||||
<option value="0">Compression (ETC1S)</option>
|
||||
<option value="1">Quality (UASTC)</option>
|
||||
</Select>
|
||||
</label>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
name="quality"
|
||||
min={options.uastc ? '0' : '1'}
|
||||
max={options.uastc ? '4' : '255'}
|
||||
step={options.uastc ? '0.1' : '1'}
|
||||
value={options.quality}
|
||||
onInput={this.onChange}
|
||||
>
|
||||
Quality:
|
||||
</Range>
|
||||
</div>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
name="compression"
|
||||
min="0"
|
||||
max="4"
|
||||
value={options.compression}
|
||||
onInput={this.onChange}
|
||||
>
|
||||
Compression:
|
||||
</Range>
|
||||
</div>
|
||||
<label class={style.optionToggle}>
|
||||
Flip Y Axis
|
||||
<Checkbox
|
||||
name="y_flip"
|
||||
checked={options.y_flip}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</label>
|
||||
<label class={style.optionReveal}>
|
||||
<Revealer
|
||||
checked={showAdvanced}
|
||||
onChange={linkState(this, 'showAdvanced')}
|
||||
/>
|
||||
Advanced settings
|
||||
</label>
|
||||
<Expander>
|
||||
{showAdvanced ? (
|
||||
<div>
|
||||
<label class={style.optionToggle}>
|
||||
Perceptual distance metric
|
||||
<Checkbox
|
||||
name="perceptual"
|
||||
checked={options.perceptual}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</label>
|
||||
<label class={style.optionToggle}>
|
||||
Embed Mipmaps
|
||||
<Checkbox
|
||||
name="mipmap"
|
||||
checked={options.mipmap}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</label>
|
||||
<Expander>
|
||||
{options.mipmap ? (
|
||||
<Fragment>
|
||||
<div class={style.optionOneCell}>
|
||||
<Range
|
||||
min="0"
|
||||
max="10"
|
||||
name="mipmap_min_dimension"
|
||||
value={Math.floor(
|
||||
Math.log2(options.mipmap_min_dimension),
|
||||
)}
|
||||
onInput={this.onChange}
|
||||
>
|
||||
Log2 of smallest mipmap:
|
||||
</Range>
|
||||
</div>
|
||||
<label class={style.optionTextFirst}>
|
||||
Resampling filter:
|
||||
<Select
|
||||
name="mipmap_filter"
|
||||
value={options.mipmap_filter}
|
||||
onChange={this.onChange}
|
||||
>
|
||||
<option value="box">Box</option>
|
||||
<option value="tent">Tent</option>
|
||||
<option value="bell">Bell</option>
|
||||
<option value="b-spline">B-Spline</option>
|
||||
<option value="mitchell">Mitchell</option>
|
||||
<option value="blackman">Blackman</option>
|
||||
<option value="lanczos3">Lanczos3</option>
|
||||
<option value="lanczos4">Lanczos4</option>
|
||||
<option value="lanczos6">Lanczos6</option>
|
||||
<option value="lanczos12">Lanczos12</option>
|
||||
<option value="kaiser">Kaiser</option>
|
||||
<option value="gaussian">Gaussian</option>
|
||||
<option value="catmullrom">Catmullrom</option>
|
||||
<option value="quadratic_interp">
|
||||
Quadratic Interpolation
|
||||
</option>
|
||||
<option value="quadratic_approx">
|
||||
Quadratic Approx
|
||||
</option>
|
||||
<option value="quadratic_mix">Quadratic Mix</option>
|
||||
</Select>
|
||||
</label>
|
||||
<label class={style.optionToggle}>
|
||||
sRGB Mipmapping
|
||||
<Checkbox
|
||||
name="srgb_mipmap"
|
||||
checked={options.srgb_mipmap}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</label>
|
||||
</Fragment>
|
||||
) : null}
|
||||
</Expander>
|
||||
</div>
|
||||
) : null}
|
||||
</Expander>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
30
src/features/encoders/basis/shared/meta.ts
Normal file
30
src/features/encoders/basis/shared/meta.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { EncodeOptions } from 'codecs/basis/enc/basis_enc';
|
||||
|
||||
export { EncodeOptions };
|
||||
|
||||
export const label = 'KTX2 (Basis Universal)';
|
||||
export const mimeType = 'image/ktx2';
|
||||
export const extension = 'ktx2';
|
||||
export const defaultOptions: EncodeOptions = {
|
||||
quality: 128,
|
||||
compression: 2,
|
||||
uastc: false,
|
||||
mipmap: false,
|
||||
srgb_mipmap: false,
|
||||
perceptual: true,
|
||||
y_flip: false,
|
||||
mipmap_filter: 'kaiser',
|
||||
mipmap_min_dimension: 1,
|
||||
};
|
13
src/features/encoders/basis/shared/missing-types.d.ts
vendored
Normal file
13
src/features/encoders/basis/shared/missing-types.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/// <reference path="../../../../../missing-types.d.ts" />
|
36
src/features/encoders/basis/worker/basisEncode.ts
Normal file
36
src/features/encoders/basis/worker/basisEncode.ts
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import type { BasisModule } from 'codecs/basis/enc/basis_enc';
|
||||
import type { EncodeOptions } from '../shared/meta';
|
||||
import { initEmscriptenModule } from 'features/worker-utils';
|
||||
|
||||
let emscriptenModule: Promise<BasisModule>;
|
||||
|
||||
async function init() {
|
||||
const basisEncoder = await import('codecs/basis/enc/basis_enc.js');
|
||||
return initEmscriptenModule(basisEncoder.default);
|
||||
}
|
||||
|
||||
export default async function encode(
|
||||
data: ImageData,
|
||||
options: EncodeOptions,
|
||||
): Promise<ArrayBuffer> {
|
||||
if (!emscriptenModule) emscriptenModule = init();
|
||||
|
||||
const module = await emscriptenModule;
|
||||
const result = module.encode(data.data, data.width, data.height, options);
|
||||
|
||||
if (!result) throw new Error('Encoding error');
|
||||
|
||||
return result.buffer;
|
||||
}
|
13
src/features/encoders/basis/worker/missing-types.d.ts
vendored
Normal file
13
src/features/encoders/basis/worker/missing-types.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Copyright 2020 Google Inc. All Rights Reserved.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/// <reference path="../../../../../missing-types.d.ts" />
|
51
src/shared/prerendered-app/Intro/SlideOnScroll/index.tsx
Normal file
51
src/shared/prerendered-app/Intro/SlideOnScroll/index.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import { h, Component, RenderableProps } from 'preact';
|
||||
|
||||
interface Props {}
|
||||
interface State {}
|
||||
|
||||
export default class SlideOnScroll extends Component<Props, State> {
|
||||
private observer?: IntersectionObserver;
|
||||
|
||||
componentDidMount() {
|
||||
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
||||
|
||||
const base = this.base as HTMLElement;
|
||||
let wasOutOfView = false;
|
||||
|
||||
this.observer = new IntersectionObserver(
|
||||
(entries, observer) => {
|
||||
for (const entry of entries) {
|
||||
if (!entry.isIntersecting) {
|
||||
wasOutOfView = true;
|
||||
base.style.opacity = '0';
|
||||
return;
|
||||
}
|
||||
|
||||
// Only transition in if the element was at some point out of view.
|
||||
if (wasOutOfView) {
|
||||
base.style.opacity = '';
|
||||
base.animate(
|
||||
{ offset: 0, opacity: '0', transform: 'translateY(40px)' },
|
||||
{ duration: 300, easing: 'ease' },
|
||||
);
|
||||
}
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
},
|
||||
{ threshold: 0.2 },
|
||||
);
|
||||
|
||||
this.observer.observe(base);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// Have to manually disconnect due to memory leaks in browsers.
|
||||
// One day we'll be able to remove this, and the private property.
|
||||
// https://twitter.com/jaffathecake/status/1405437361643790337
|
||||
if (this.observer) this.observer.disconnect();
|
||||
}
|
||||
|
||||
render({ children }: RenderableProps<Props>) {
|
||||
return <div>{children}</div>;
|
||||
}
|
||||
}
|
@ -329,10 +329,11 @@ export function startBlobAnim(canvas: HTMLCanvasElement) {
|
||||
hasFocus = false;
|
||||
};
|
||||
|
||||
new ResizeObserver(() => {
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
// Redraw for new canvas size
|
||||
if (!animating) drawFrame(0);
|
||||
}).observe(canvas);
|
||||
});
|
||||
resizeObserver.observe(canvas);
|
||||
|
||||
addEventListener('focus', focusListener);
|
||||
addEventListener('blur', blurListener);
|
||||
@ -341,6 +342,7 @@ export function startBlobAnim(canvas: HTMLCanvasElement) {
|
||||
function destruct() {
|
||||
removeEventListener('focus', focusListener);
|
||||
removeEventListener('blur', blurListener);
|
||||
resizeObserver.disconnect();
|
||||
document.removeEventListener('visibilitychange', visibilityListener);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
<svg width="498" height="333" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M401.17 125.52C387.072 53.923 324.253.173 248.787.173c-59.916 0-111.954 34.035-137.869 83.841C48.513 90.655 0 143.574 0 207.7c0 68.692 55.77 124.516 124.394 124.516h269.519c57.221 0 103.662-46.486 103.662-103.763 0-54.787-42.502-99.198-96.405-102.933z" fill="#91D3FF" fill-opacity=".3"/><path d="M187.247 121.321l-3.987-3.987-3.481 4.434c-11.519 14.67-18.366 33.15-18.366 53.242 0 48.003 38.882 86.885 86.885 86.885 20.091 0 38.572-6.848 53.242-18.366l4.434-3.482-3.987-3.986-114.74-114.74zM309.348 228.7l3.987 3.986 3.481-4.434c11.519-14.669 18.366-33.15 18.366-53.242 0-48.002-38.882-86.884-86.884-86.884-20.092 0-38.573 6.847-53.242 18.365l-4.435 3.482 3.987 3.986L309.348 228.7zm-158.406-53.69c0-53.739 43.617-97.355 97.356-97.355 53.738 0 97.355 43.616 97.355 97.355 0 53.739-43.617 97.356-97.355 97.356-53.739 0-97.356-43.617-97.356-97.356z" fill="#FF3385" stroke="#FF3385" stroke-width="10"/></svg>
|
After Width: | Height: | Size: 991 B |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.2 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 5.4 KiB |
@ -10,12 +10,16 @@ import deviceScreen from 'url:./imgs/demos/demo-device-screen.png';
|
||||
import largePhotoIcon from 'url:./imgs/demos/icon-demo-large-photo.jpg';
|
||||
import artworkIcon from 'url:./imgs/demos/icon-demo-artwork.jpg';
|
||||
import deviceScreenIcon from 'url:./imgs/demos/icon-demo-device-screen.jpg';
|
||||
import smallSectionAsset from 'url:./imgs/info-content/small.svg';
|
||||
import simpleSectionAsset from 'url:./imgs/info-content/simple.svg';
|
||||
import secureSectionAsset from 'url:./imgs/info-content/secure.svg';
|
||||
import logoIcon from 'url:./imgs/demos/icon-demo-logo.png';
|
||||
import logoWithText from 'url:./imgs/logo-with-text.svg';
|
||||
import * as style from './style.css';
|
||||
import type SnackBarElement from 'shared/custom-els/snack-bar';
|
||||
import 'shared/custom-els/snack-bar';
|
||||
import { startBlobs } from './blob-anim/meta';
|
||||
import SlideOnScroll from './SlideOnScroll';
|
||||
|
||||
const demos = [
|
||||
{
|
||||
@ -336,37 +340,125 @@ export default class Intro extends Component<Props, State> {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class={style.footer}>
|
||||
|
||||
<div class={style.bottomWave}>
|
||||
<svg viewBox="0 0 1920 79" class={style.topWave}>
|
||||
<path
|
||||
d="M0 59l64-11c64-11 192-34 320-43s256-5 384 4 256 23 384 34 256 21 384 14 256-30 320-41l64-11v94H0z"
|
||||
class={style.footerWave}
|
||||
class={style.infoWave}
|
||||
/>
|
||||
</svg>
|
||||
<div class={style.contentPadding}>
|
||||
<footer class={style.footerItems}>
|
||||
<a
|
||||
class={style.footerLink}
|
||||
href="https://github.com/GoogleChromeLabs/squoosh/blob/dev/README.md#privacy"
|
||||
>
|
||||
Privacy
|
||||
</a>
|
||||
<a
|
||||
class={style.footerLink}
|
||||
href="https://github.com/GoogleChromeLabs/squoosh/tree/dev/cli"
|
||||
>
|
||||
Squoosh CLI
|
||||
</a>
|
||||
<a
|
||||
class={style.footerLinkWithLogo}
|
||||
href="https://github.com/GoogleChromeLabs/squoosh"
|
||||
>
|
||||
<img src={githubLogo} alt="" width="10" height="10" />
|
||||
Source on Github
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class={style.info}>
|
||||
<div class={style.infoContainer}>
|
||||
<SlideOnScroll>
|
||||
<div class={style.infoContent}>
|
||||
<div class={style.infoTextWrapper}>
|
||||
<h2 class={style.infoTitle}>Small</h2>
|
||||
<p class={style.infoCaption}>
|
||||
Smaller images mean faster load times. Squoosh can reduce
|
||||
file size and maintain high quality.
|
||||
</p>
|
||||
</div>
|
||||
<div class={style.infoImgWrapper}>
|
||||
<img
|
||||
class={style.infoImg}
|
||||
src={smallSectionAsset}
|
||||
alt="silhouette of a large 1.4 megabyte image shrunk into a smaller 80 kilobyte image"
|
||||
width="536"
|
||||
height="522"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SlideOnScroll>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class={style.info}>
|
||||
<div class={style.infoContainer}>
|
||||
<SlideOnScroll>
|
||||
<div class={style.infoContent}>
|
||||
<div class={style.infoTextWrapper}>
|
||||
<h2 class={style.infoTitle}>Simple</h2>
|
||||
<p class={style.infoCaption}>
|
||||
Open your image, inspect the differences, then save
|
||||
instantly. Feeling adventurous? Adjust the settings for even
|
||||
smaller files.
|
||||
</p>
|
||||
</div>
|
||||
<div class={style.infoImgWrapper}>
|
||||
<img
|
||||
class={style.infoImg}
|
||||
src={simpleSectionAsset}
|
||||
alt="grid of multiple shrunk images displaying various options"
|
||||
width="538"
|
||||
height="384"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SlideOnScroll>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class={style.info}>
|
||||
<div class={style.infoContainer}>
|
||||
<SlideOnScroll>
|
||||
<div class={style.infoContent}>
|
||||
<div class={style.infoTextWrapper}>
|
||||
<h2 class={style.infoTitle}>Secure</h2>
|
||||
<p class={style.infoCaption}>
|
||||
Worried about privacy? Images never leave your device since
|
||||
Squoosh does all the work locally.
|
||||
</p>
|
||||
</div>
|
||||
<div class={style.infoImgWrapper}>
|
||||
<img
|
||||
class={style.infoImg}
|
||||
src={secureSectionAsset}
|
||||
alt="silhouette of a cloud with a 'no' symbol on top"
|
||||
width="498"
|
||||
height="333"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SlideOnScroll>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class={style.footer}>
|
||||
<div class={style.footerContainer}>
|
||||
<svg viewBox="0 0 1920 79" class={style.topWave}>
|
||||
<path
|
||||
d="M0 59l64-11c64-11 192-34 320-43s256-5 384 4 256 23 384 34 256 21 384 14 256-30 320-41l64-11v94H0z"
|
||||
class={style.footerWave}
|
||||
/>
|
||||
</svg>
|
||||
<div class={style.footerPadding}>
|
||||
<footer class={style.footerItems}>
|
||||
<a
|
||||
class={style.footerLink}
|
||||
href="https://github.com/GoogleChromeLabs/squoosh/blob/dev/README.md#privacy"
|
||||
>
|
||||
Privacy
|
||||
</a>
|
||||
<a
|
||||
class={style.footerLink}
|
||||
href="https://github.com/GoogleChromeLabs/squoosh/tree/dev/cli"
|
||||
>
|
||||
Squoosh CLI
|
||||
</a>
|
||||
<a
|
||||
class={style.footerLinkWithLogo}
|
||||
href="https://github.com/GoogleChromeLabs/squoosh"
|
||||
>
|
||||
<img src={githubLogo} alt="" width="10" height="10" />
|
||||
Source on Github
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{beforeInstallEvent && (
|
||||
<button class={style.installBtn} onClick={this.onInstallClick}>
|
||||
Install
|
||||
|
@ -118,7 +118,114 @@
|
||||
fill: var(--light-blue);
|
||||
}
|
||||
|
||||
.bottom-wave {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.info {
|
||||
background: var(--white);
|
||||
position: relative;
|
||||
padding: 5em 2em;
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
padding: 5em 0;
|
||||
}
|
||||
}
|
||||
|
||||
.info-container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.info-content {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1em;
|
||||
|
||||
grid-template-areas:
|
||||
'text'
|
||||
'img';
|
||||
|
||||
@media (min-width: 712px) {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-template-areas: 'text img';
|
||||
|
||||
.info:nth-child(even) & {
|
||||
grid-template-areas: 'img text';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-title {
|
||||
color: var(--pink);
|
||||
font-size: 3em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.info-caption {
|
||||
font-size: 1.5em;
|
||||
line-height: 1.75;
|
||||
margin: 1em 0 0.5em 0;
|
||||
}
|
||||
|
||||
.info-link {
|
||||
font-size: 1.25em;
|
||||
text-underline-offset: 0.25em;
|
||||
color: var(--off-black);
|
||||
transition: color 400ms ease-in-out;
|
||||
margin-top: 1em;
|
||||
&:hover {
|
||||
color: var(--dim-blue);
|
||||
}
|
||||
}
|
||||
|
||||
.info-text-wrapper {
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
max-width: 27em;
|
||||
justify-self: center;
|
||||
grid-area: text;
|
||||
|
||||
@media (min-width: 712px) {
|
||||
justify-self: start;
|
||||
.info:nth-child(even) & {
|
||||
text-align: right;
|
||||
justify-self: end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-img-wrapper {
|
||||
grid-area: img;
|
||||
}
|
||||
|
||||
.info-img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
margin: 0 0 0 auto;
|
||||
|
||||
.info:nth-child(even) & {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-wave {
|
||||
fill: var(--white);
|
||||
}
|
||||
|
||||
.footer {
|
||||
background: var(--white);
|
||||
padding-top: 5em;
|
||||
}
|
||||
|
||||
.footer-container {
|
||||
position: relative;
|
||||
background: var(--light-gray);
|
||||
}
|
||||
@ -128,7 +235,11 @@
|
||||
}
|
||||
|
||||
.content-padding {
|
||||
padding: 2rem;
|
||||
padding: 2em 0;
|
||||
}
|
||||
|
||||
.footer-padding {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.footer-items {
|
||||
|
@ -16,7 +16,8 @@ import baseCss from 'css:./base.css';
|
||||
import initialCss from 'initial-css:';
|
||||
import { allSrc } from 'client-bundle:client/initial-app';
|
||||
import favicon from 'url:static-build/assets/favicon.ico';
|
||||
import { escapeStyleScriptContent } from 'static-build/utils';
|
||||
import ogImage from 'url:static-build/assets/icon-large-maskable.png';
|
||||
import { escapeStyleScriptContent, siteOrigin } from 'static-build/utils';
|
||||
import Intro from 'shared/prerendered-app/Intro';
|
||||
|
||||
interface Props {}
|
||||
@ -27,7 +28,27 @@ const Index: FunctionalComponent<Props> = () => (
|
||||
<title>Squoosh</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Compress and compare images with different codecs, right in your browser"
|
||||
content="Squoosh is the ultimate image optimizer that allows you to compress and compare images with different codecs in your browser."
|
||||
/>
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="twitter:site" content="@SquooshApp" />
|
||||
<meta property="og:title" content="Squoosh" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:image" content={`${siteOrigin}${ogImage}`} />
|
||||
<meta
|
||||
property="og:image:secure_url"
|
||||
content={`${siteOrigin}${ogImage}`}
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="500" />
|
||||
<meta property="og:image:height" content="500" />
|
||||
<meta
|
||||
property="og:image:alt"
|
||||
content="A cartoon of a hand squeezing an image file on a dark background."
|
||||
/>
|
||||
<meta
|
||||
name="og:description"
|
||||
content="Squoosh is the ultimate image optimizer that allows you to compress and compare images with different codecs in your browser."
|
||||
/>
|
||||
<meta
|
||||
name="viewport"
|
||||
@ -38,6 +59,7 @@ const Index: FunctionalComponent<Props> = () => (
|
||||
<link rel="shortcut icon" href={favicon} />
|
||||
<meta name="theme-color" content="#ff3385" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="canonical" href={siteOrigin} />
|
||||
<style
|
||||
dangerouslySetInnerHTML={{ __html: escapeStyleScriptContent(baseCss) }}
|
||||
/>
|
||||
|
@ -56,3 +56,17 @@ export function escapeStyleScriptContent(str: string): string {
|
||||
.replace(/<style/g, '<\\style')
|
||||
.replace(/<\/style/g, '<\\/style');
|
||||
}
|
||||
|
||||
/**
|
||||
* Origin of the site, depending on the environment.
|
||||
*/
|
||||
export const siteOrigin = (() => {
|
||||
if (process.env.DEV_PORT) return `http://localhost:${process.env.DEV_PORT}`;
|
||||
// https://docs.netlify.com/configure-builds/environment-variables/#build-metadata
|
||||
if (process.env.CONTEXT === 'production') return 'https://squoosh.app';
|
||||
if (process.env.DEPLOY_PRIME_URL) return process.env.DEPLOY_PRIME_URL;
|
||||
console.warn(
|
||||
'Unable to determine site origin, defaulting to https://squoosh.app',
|
||||
);
|
||||
return 'https://squoosh.app';
|
||||
})();
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": "./generic-tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["esnext", "dom"],
|
||||
"lib": ["esnext", "dom", "dom.iterable"],
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/shared/**/*", "src/static-build/**/*"]
|
||||
|
Reference in New Issue
Block a user