├── .cargo └── config.toml ├── .editorconfig ├── .eslintrc.yml ├── .gitattributes ├── .github ├── renovate.json └── workflows │ ├── CI.yml │ └── lint.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierignore ├── .taplo.toml ├── .yarn └── releases │ └── yarn-3.4.1.cjs ├── .yarnrc.yml ├── Cargo.toml ├── LICENSE ├── README.md ├── __test__ └── index.spec.ts ├── benchmark ├── bench.ts └── fixture │ └── img.png ├── build.rs ├── index.d.ts ├── index.js ├── npm ├── android-arm-eabi │ ├── README.md │ └── package.json ├── android-arm64 │ ├── README.md │ └── package.json ├── darwin-arm64 │ ├── README.md │ └── package.json ├── darwin-x64 │ ├── README.md │ └── package.json ├── freebsd-x64 │ ├── README.md │ └── package.json ├── linux-arm-gnueabihf │ ├── README.md │ └── package.json ├── linux-arm64-gnu │ ├── README.md │ └── package.json ├── linux-arm64-musl │ ├── README.md │ └── package.json ├── linux-x64-gnu │ ├── README.md │ └── package.json ├── linux-x64-musl │ ├── README.md │ └── package.json ├── win32-arm64-msvc │ ├── README.md │ └── package.json ├── win32-ia32-msvc │ ├── README.md │ └── package.json └── win32-x64-msvc │ ├── README.md │ └── package.json ├── package.json ├── rustfmt.toml ├── simple-test.js ├── src └── lib.rs ├── tsconfig.json └── yarn.lock /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-musl] 2 | linker = "aarch64-linux-musl-gcc" 3 | rustflags = ["-C", "target-feature=-crt-static"] 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors or IDEs 3 | # http://editorconfig.org 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | parser: '@typescript-eslint/parser' 2 | 3 | parserOptions: 4 | ecmaFeatures: 5 | jsx: true 6 | ecmaVersion: latest 7 | sourceType: module 8 | project: ./tsconfig.json 9 | 10 | env: 11 | browser: true 12 | es6: true 13 | node: true 14 | jest: true 15 | 16 | ignorePatterns: ['index.js'] 17 | 18 | plugins: 19 | - import 20 | - '@typescript-eslint' 21 | 22 | extends: 23 | - eslint:recommended 24 | - plugin:prettier/recommended 25 | 26 | rules: 27 | # 0 = off, 1 = warn, 2 = error 28 | 'space-before-function-paren': 0 29 | 'no-useless-constructor': 0 30 | 'no-undef': 2 31 | 'no-console': [2, { allow: ['error', 'warn', 'info', 'assert'] }] 32 | 'comma-dangle': ['error', 'only-multiline'] 33 | 'no-unused-vars': 0 34 | 'no-var': 2 35 | 'one-var-declaration-per-line': 2 36 | 'prefer-const': 2 37 | 'no-const-assign': 2 38 | 'no-duplicate-imports': 2 39 | 'no-use-before-define': [2, { 'functions': false, 'classes': false }] 40 | 'eqeqeq': [2, 'always', { 'null': 'ignore' }] 41 | 'no-case-declarations': 0 42 | 'no-restricted-syntax': 43 | [ 44 | 2, 45 | { 46 | 'selector': 'BinaryExpression[operator=/(==|===|!=|!==)/][left.raw=true], BinaryExpression[operator=/(==|===|!=|!==)/][right.raw=true]', 47 | 'message': Don't compare for equality against boolean literals, 48 | }, 49 | ] 50 | 51 | # https://github.com/benmosher/eslint-plugin-import/pull/334 52 | 'import/no-duplicates': 2 53 | 'import/first': 2 54 | 'import/newline-after-import': 2 55 | 'import/order': 56 | [ 57 | 2, 58 | { 59 | 'newlines-between': 'always', 60 | 'alphabetize': { 'order': 'asc' }, 61 | 'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], 62 | }, 63 | ] 64 | 65 | overrides: 66 | - files: 67 | - ./**/*{.ts,.tsx} 68 | rules: 69 | 'no-unused-vars': [2, { varsIgnorePattern: '^_', argsIgnorePattern: '^_', ignoreRestSiblings: true }] 70 | 'no-undef': 0 71 | # TypeScript declare merge 72 | 'no-redeclare': 0 73 | 'no-useless-constructor': 0 74 | 'no-dupe-class-members': 0 75 | 'no-case-declarations': 0 76 | 'no-duplicate-imports': 0 77 | # TypeScript Interface and Type 78 | 'no-use-before-define': 0 79 | 80 | '@typescript-eslint/adjacent-overload-signatures': 2 81 | '@typescript-eslint/await-thenable': 2 82 | '@typescript-eslint/consistent-type-assertions': 2 83 | '@typescript-eslint/ban-types': 84 | [ 85 | 'error', 86 | { 87 | 'types': 88 | { 89 | 'String': { 'message': 'Use string instead', 'fixWith': 'string' }, 90 | 'Number': { 'message': 'Use number instead', 'fixWith': 'number' }, 91 | 'Boolean': { 'message': 'Use boolean instead', 'fixWith': 'boolean' }, 92 | 'Function': { 'message': 'Use explicit type instead' }, 93 | }, 94 | }, 95 | ] 96 | '@typescript-eslint/explicit-member-accessibility': 97 | [ 98 | 'error', 99 | { 100 | accessibility: 'explicit', 101 | overrides: 102 | { 103 | accessors: 'no-public', 104 | constructors: 'no-public', 105 | methods: 'no-public', 106 | properties: 'no-public', 107 | parameterProperties: 'explicit', 108 | }, 109 | }, 110 | ] 111 | '@typescript-eslint/method-signature-style': 2 112 | '@typescript-eslint/no-floating-promises': 2 113 | '@typescript-eslint/no-implied-eval': 2 114 | '@typescript-eslint/no-for-in-array': 2 115 | '@typescript-eslint/no-inferrable-types': 2 116 | '@typescript-eslint/no-invalid-void-type': 2 117 | '@typescript-eslint/no-misused-new': 2 118 | '@typescript-eslint/no-misused-promises': 2 119 | '@typescript-eslint/no-namespace': 2 120 | '@typescript-eslint/no-non-null-asserted-optional-chain': 2 121 | '@typescript-eslint/no-throw-literal': 2 122 | '@typescript-eslint/no-unnecessary-boolean-literal-compare': 2 123 | '@typescript-eslint/prefer-for-of': 2 124 | '@typescript-eslint/prefer-nullish-coalescing': 2 125 | '@typescript-eslint/switch-exhaustiveness-check': 2 126 | '@typescript-eslint/prefer-optional-chain': 2 127 | '@typescript-eslint/prefer-readonly': 2 128 | '@typescript-eslint/prefer-string-starts-ends-with': 0 129 | '@typescript-eslint/no-array-constructor': 2 130 | '@typescript-eslint/require-await': 2 131 | '@typescript-eslint/return-await': 2 132 | '@typescript-eslint/ban-ts-comment': 133 | [2, { 'ts-expect-error': false, 'ts-ignore': true, 'ts-nocheck': true, 'ts-check': false }] 134 | '@typescript-eslint/naming-convention': 135 | [ 136 | 2, 137 | { 138 | selector: 'memberLike', 139 | format: ['camelCase', 'PascalCase'], 140 | modifiers: ['private'], 141 | leadingUnderscore: 'forbid', 142 | }, 143 | ] 144 | '@typescript-eslint/no-unused-vars': 145 | [2, { varsIgnorePattern: '^_', argsIgnorePattern: '^_', ignoreRestSiblings: true }] 146 | '@typescript-eslint/member-ordering': 147 | [ 148 | 2, 149 | { 150 | default: 151 | [ 152 | 'public-static-field', 153 | 'protected-static-field', 154 | 'private-static-field', 155 | 'public-static-method', 156 | 'protected-static-method', 157 | 'private-static-method', 158 | 'public-instance-field', 159 | 'protected-instance-field', 160 | 'private-instance-field', 161 | 'public-constructor', 162 | 'protected-constructor', 163 | 'private-constructor', 164 | 'public-instance-method', 165 | 'protected-instance-method', 166 | 'private-instance-method', 167 | ], 168 | }, 169 | ] 170 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | 5 | *.ts text eol=lf merge=union 6 | *.tsx text eol=lf merge=union 7 | *.rs text eol=lf merge=union 8 | *.js text eol=lf merge=union 9 | *.json text eol=lf merge=union 10 | *.debug text eol=lf merge=union 11 | 12 | # Generated codes 13 | index.js linguist-detectable=false 14 | index.d.ts linguist-detectable=false -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base", "group:allNonMajor", ":preserveSemverRanges", ":disablePeerDependencies"], 4 | "labels": ["dependencies"], 5 | "packageRules": [ 6 | { 7 | "matchPackageNames": ["@napi/cli", "napi", "napi-build", "napi-derive"], 8 | "addLabels": ["napi-rs"], 9 | "groupName": "napi-rs" 10 | }, 11 | { 12 | "matchPackagePatterns": ["^eslint", "^@typescript-eslint"], 13 | "groupName": "linter" 14 | } 15 | ], 16 | "commitMessagePrefix": "chore: ", 17 | "commitMessageAction": "bump up", 18 | "commitMessageTopic": "{{depName}} version", 19 | "ignoreDeps": [] 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | env: 3 | DEBUG: napi:* 4 | APP_NAME: thumbhash-node 5 | MACOSX_DEPLOYMENT_TARGET: '10.13' 6 | 'on': 7 | push: 8 | branches: 9 | - main 10 | tags-ignore: 11 | - '**' 12 | paths-ignore: 13 | - '**/*.md' 14 | - LICENSE 15 | - '**/*.gitignore' 16 | - .editorconfig 17 | - docs/** 18 | pull_request: null 19 | concurrency: 20 | group: ${{ github.workflow }}-${{ github.ref }} 21 | cancel-in-progress: true 22 | jobs: 23 | build: 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | settings: 28 | - host: macos-latest 29 | target: x86_64-apple-darwin 30 | build: | 31 | yarn build 32 | strip -x *.node 33 | - host: windows-latest 34 | build: yarn build 35 | target: x86_64-pc-windows-msvc 36 | - host: windows-latest 37 | build: | 38 | yarn build --target i686-pc-windows-msvc 39 | yarn test 40 | target: i686-pc-windows-msvc 41 | - host: ubuntu-latest 42 | target: x86_64-unknown-linux-gnu 43 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian 44 | build: |- 45 | set -e && 46 | yarn build --target x86_64-unknown-linux-gnu && 47 | strip *.node 48 | - host: ubuntu-latest 49 | target: x86_64-unknown-linux-musl 50 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine 51 | build: set -e && yarn build && strip *.node 52 | - host: macos-latest 53 | target: aarch64-apple-darwin 54 | build: | 55 | sudo rm -Rf /Library/Developer/CommandLineTools/SDKs/*; 56 | export CC=$(xcrun -f clang); 57 | export CXX=$(xcrun -f clang++); 58 | SYSROOT=$(xcrun --sdk macosx --show-sdk-path); 59 | export CFLAGS="-isysroot $SYSROOT -isystem $SYSROOT"; 60 | yarn build --target aarch64-apple-darwin 61 | strip -x *.node 62 | - host: ubuntu-latest 63 | target: aarch64-unknown-linux-gnu 64 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64 65 | build: |- 66 | set -e && 67 | yarn build --target aarch64-unknown-linux-gnu && 68 | aarch64-unknown-linux-gnu-strip *.node 69 | - host: ubuntu-latest 70 | target: armv7-unknown-linux-gnueabihf 71 | setup: | 72 | sudo apt-get update 73 | sudo apt-get install gcc-arm-linux-gnueabihf -y 74 | build: | 75 | yarn build --target armv7-unknown-linux-gnueabihf 76 | arm-linux-gnueabihf-strip *.node 77 | - host: ubuntu-latest 78 | target: aarch64-linux-android 79 | build: | 80 | yarn build --target aarch64-linux-android 81 | ${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip *.node 82 | - host: ubuntu-latest 83 | target: armv7-linux-androideabi 84 | build: | 85 | yarn build --target armv7-linux-androideabi 86 | ${ANDROID_NDK_LATEST_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/llvm-strip *.node 87 | - host: ubuntu-latest 88 | target: aarch64-unknown-linux-musl 89 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine 90 | build: |- 91 | set -e && 92 | rustup target add aarch64-unknown-linux-musl && 93 | yarn build --target aarch64-unknown-linux-musl && 94 | /aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip *.node 95 | - host: windows-latest 96 | target: aarch64-pc-windows-msvc 97 | build: yarn build --target aarch64-pc-windows-msvc 98 | name: stable - ${{ matrix.settings.target }} - node@18 99 | runs-on: ${{ matrix.settings.host }} 100 | steps: 101 | - uses: actions/checkout@v3 102 | - name: Setup node 103 | uses: actions/setup-node@v3 104 | if: ${{ !matrix.settings.docker }} 105 | with: 106 | node-version: 18 107 | check-latest: true 108 | cache: yarn 109 | - name: Install 110 | uses: dtolnay/rust-toolchain@stable 111 | if: ${{ !matrix.settings.docker }} 112 | with: 113 | toolchain: stable 114 | targets: ${{ matrix.settings.target }} 115 | - name: Cache cargo 116 | uses: actions/cache@v3 117 | with: 118 | path: | 119 | ~/.cargo/registry/index/ 120 | ~/.cargo/registry/cache/ 121 | ~/.cargo/git/db/ 122 | .cargo-cache 123 | target/ 124 | key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }} 125 | - uses: goto-bus-stop/setup-zig@v2 126 | if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' }} 127 | with: 128 | version: 0.10.1 129 | - name: Setup toolchain 130 | run: ${{ matrix.settings.setup }} 131 | if: ${{ matrix.settings.setup }} 132 | shell: bash 133 | - name: Setup node x86 134 | if: matrix.settings.target == 'i686-pc-windows-msvc' 135 | run: yarn config set supportedArchitectures.cpu "ia32" 136 | shell: bash 137 | - name: Install dependencies 138 | run: yarn install 139 | - name: Setup node x86 140 | uses: actions/setup-node@v3 141 | if: matrix.settings.target == 'i686-pc-windows-msvc' 142 | with: 143 | node-version: 18 144 | check-latest: true 145 | cache: yarn 146 | architecture: x86 147 | - name: Build in docker 148 | uses: addnab/docker-run-action@v3 149 | if: ${{ matrix.settings.docker }} 150 | with: 151 | image: ${{ matrix.settings.docker }} 152 | options: '--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build' 153 | run: ${{ matrix.settings.build }} 154 | - name: Build 155 | run: ${{ matrix.settings.build }} 156 | if: ${{ !matrix.settings.docker }} 157 | shell: bash 158 | - name: Upload artifact 159 | uses: actions/upload-artifact@v3 160 | with: 161 | name: bindings-${{ matrix.settings.target }} 162 | path: ${{ env.APP_NAME }}.*.node 163 | if-no-files-found: error 164 | build-freebsd: 165 | runs-on: macos-12 166 | name: Build FreeBSD 167 | steps: 168 | - uses: actions/checkout@v3 169 | - name: Build 170 | id: build 171 | uses: vmactions/freebsd-vm@v0 172 | env: 173 | DEBUG: napi:* 174 | RUSTUP_HOME: /usr/local/rustup 175 | CARGO_HOME: /usr/local/cargo 176 | RUSTUP_IO_THREADS: 1 177 | with: 178 | envs: DEBUG RUSTUP_HOME CARGO_HOME RUSTUP_IO_THREADS 179 | usesh: true 180 | mem: 3000 181 | prepare: | 182 | pkg install -y -f curl node libnghttp2 183 | curl -qL https://www.npmjs.com/install.sh | sh 184 | npm install --location=global --ignore-scripts yarn 185 | curl https://sh.rustup.rs -sSf --output rustup.sh 186 | sh rustup.sh -y --profile minimal --default-toolchain beta 187 | export PATH="/usr/local/cargo/bin:$PATH" 188 | echo "~~~~ rustc --version ~~~~" 189 | rustc --version 190 | echo "~~~~ node -v ~~~~" 191 | node -v 192 | echo "~~~~ yarn --version ~~~~" 193 | yarn --version 194 | run: | 195 | export PATH="/usr/local/cargo/bin:$PATH" 196 | pwd 197 | ls -lah 198 | whoami 199 | env 200 | freebsd-version 201 | yarn install 202 | yarn build 203 | strip -x *.node 204 | yarn test 205 | rm -rf node_modules 206 | rm -rf target 207 | rm -rf .yarn/cache 208 | - name: Upload artifact 209 | uses: actions/upload-artifact@v3 210 | with: 211 | name: bindings-freebsd 212 | path: ${{ env.APP_NAME }}.*.node 213 | if-no-files-found: error 214 | test-macOS-windows-binding: 215 | name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }} 216 | needs: 217 | - build 218 | strategy: 219 | fail-fast: false 220 | matrix: 221 | settings: 222 | - host: windows-latest 223 | target: x86_64-pc-windows-msvc 224 | node: 225 | - '14' 226 | - '16' 227 | - '18' 228 | runs-on: ${{ matrix.settings.host }} 229 | steps: 230 | - uses: actions/checkout@v3 231 | - name: Setup node 232 | uses: actions/setup-node@v3 233 | with: 234 | node-version: ${{ matrix.node }} 235 | check-latest: true 236 | cache: yarn 237 | - name: Install dependencies 238 | run: yarn install 239 | - name: Download artifacts 240 | uses: actions/download-artifact@v3 241 | with: 242 | name: bindings-${{ matrix.settings.target }} 243 | path: . 244 | - name: List packages 245 | run: ls -R . 246 | shell: bash 247 | - name: Test bindings 248 | run: yarn test 249 | test-linux-x64-gnu-binding: 250 | name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }} 251 | needs: 252 | - build 253 | strategy: 254 | fail-fast: false 255 | matrix: 256 | node: 257 | - '14' 258 | - '16' 259 | - '18' 260 | runs-on: ubuntu-latest 261 | steps: 262 | - uses: actions/checkout@v3 263 | - name: Setup node 264 | uses: actions/setup-node@v3 265 | with: 266 | node-version: ${{ matrix.node }} 267 | check-latest: true 268 | cache: yarn 269 | - name: Install dependencies 270 | run: yarn install 271 | - name: Download artifacts 272 | uses: actions/download-artifact@v3 273 | with: 274 | name: bindings-x86_64-unknown-linux-gnu 275 | path: . 276 | - name: List packages 277 | run: ls -R . 278 | shell: bash 279 | - name: Test bindings 280 | run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-slim yarn test 281 | test-linux-x64-musl-binding: 282 | name: Test bindings on x86_64-unknown-linux-musl - node@${{ matrix.node }} 283 | needs: 284 | - build 285 | strategy: 286 | fail-fast: false 287 | matrix: 288 | node: 289 | - '14' 290 | - '16' 291 | - '18' 292 | runs-on: ubuntu-latest 293 | steps: 294 | - uses: actions/checkout@v3 295 | - name: Setup node 296 | uses: actions/setup-node@v3 297 | with: 298 | node-version: ${{ matrix.node }} 299 | check-latest: true 300 | cache: yarn 301 | - name: Install dependencies 302 | run: | 303 | yarn config set supportedArchitectures.libc "musl" 304 | yarn install 305 | - name: Download artifacts 306 | uses: actions/download-artifact@v3 307 | with: 308 | name: bindings-x86_64-unknown-linux-musl 309 | path: . 310 | - name: List packages 311 | run: ls -R . 312 | shell: bash 313 | - name: Test bindings 314 | run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-alpine yarn test 315 | test-linux-aarch64-gnu-binding: 316 | name: Test bindings on aarch64-unknown-linux-gnu - node@${{ matrix.node }} 317 | needs: 318 | - build 319 | strategy: 320 | fail-fast: false 321 | matrix: 322 | node: 323 | - '14' 324 | - '16' 325 | - '18' 326 | runs-on: ubuntu-latest 327 | steps: 328 | - uses: actions/checkout@v3 329 | - name: Download artifacts 330 | uses: actions/download-artifact@v3 331 | with: 332 | name: bindings-aarch64-unknown-linux-gnu 333 | path: . 334 | - name: List packages 335 | run: ls -R . 336 | shell: bash 337 | - name: Install dependencies 338 | run: | 339 | yarn config set supportedArchitectures.cpu "arm64" 340 | yarn config set supportedArchitectures.libc "glibc" 341 | yarn install 342 | - name: Set up QEMU 343 | uses: docker/setup-qemu-action@v2 344 | with: 345 | platforms: arm64 346 | - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 347 | - name: Setup and run tests 348 | uses: addnab/docker-run-action@v3 349 | with: 350 | image: node:${{ matrix.node }}-slim 351 | options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build' 352 | run: | 353 | set -e 354 | yarn test 355 | ls -la 356 | test-linux-aarch64-musl-binding: 357 | name: Test bindings on aarch64-unknown-linux-musl - node@${{ matrix.node }} 358 | needs: 359 | - build 360 | runs-on: ubuntu-latest 361 | steps: 362 | - uses: actions/checkout@v3 363 | - name: Download artifacts 364 | uses: actions/download-artifact@v3 365 | with: 366 | name: bindings-aarch64-unknown-linux-musl 367 | path: . 368 | - name: List packages 369 | run: ls -R . 370 | shell: bash 371 | - name: Install dependencies 372 | run: | 373 | yarn config set supportedArchitectures.cpu "arm64" 374 | yarn config set supportedArchitectures.libc "musl" 375 | yarn install 376 | - name: Set up QEMU 377 | uses: docker/setup-qemu-action@v2 378 | with: 379 | platforms: arm64 380 | - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 381 | - name: Setup and run tests 382 | uses: addnab/docker-run-action@v3 383 | with: 384 | image: node:lts-alpine 385 | options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build' 386 | run: | 387 | set -e 388 | yarn test 389 | test-linux-arm-gnueabihf-binding: 390 | name: Test bindings on armv7-unknown-linux-gnueabihf - node@${{ matrix.node }} 391 | needs: 392 | - build 393 | strategy: 394 | fail-fast: false 395 | matrix: 396 | node: 397 | - '14' 398 | - '16' 399 | - '18' 400 | runs-on: ubuntu-latest 401 | steps: 402 | - uses: actions/checkout@v3 403 | - name: Download artifacts 404 | uses: actions/download-artifact@v3 405 | with: 406 | name: bindings-armv7-unknown-linux-gnueabihf 407 | path: . 408 | - name: List packages 409 | run: ls -R . 410 | shell: bash 411 | - name: Install dependencies 412 | run: | 413 | yarn config set supportedArchitectures.cpu "arm" 414 | yarn install 415 | - name: Set up QEMU 416 | uses: docker/setup-qemu-action@v2 417 | with: 418 | platforms: arm 419 | - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 420 | - name: Setup and run tests 421 | uses: addnab/docker-run-action@v3 422 | with: 423 | image: node:${{ matrix.node }}-bullseye-slim 424 | options: '--platform linux/arm/v7 -v ${{ github.workspace }}:/build -w /build' 425 | run: | 426 | set -e 427 | yarn test 428 | ls -la 429 | publish: 430 | name: Publish 431 | runs-on: ubuntu-latest 432 | needs: 433 | - build-freebsd 434 | - test-macOS-windows-binding 435 | - test-linux-x64-gnu-binding 436 | - test-linux-x64-musl-binding 437 | - test-linux-aarch64-gnu-binding 438 | - test-linux-aarch64-musl-binding 439 | - test-linux-arm-gnueabihf-binding 440 | steps: 441 | - uses: actions/checkout@v3 442 | - name: Setup node 443 | uses: actions/setup-node@v3 444 | with: 445 | node-version: 18 446 | check-latest: true 447 | cache: yarn 448 | - name: Install dependencies 449 | run: yarn install 450 | - name: Download all artifacts 451 | uses: actions/download-artifact@v3 452 | with: 453 | path: artifacts 454 | - name: Move artifacts 455 | run: yarn artifacts 456 | - name: List packages 457 | run: ls -R ./npm 458 | shell: bash 459 | - name: Publish 460 | run: | 461 | if git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+$"; 462 | then 463 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 464 | npm publish --access public 465 | elif git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+"; 466 | then 467 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 468 | npm publish --tag next --access public 469 | else 470 | echo "Not a release, skipping publish" 471 | fi 472 | env: 473 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 474 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 475 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags-ignore: 8 | - '**' 9 | pull_request: 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | jobs: 14 | lint: 15 | name: Lint 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Setup node 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 18 24 | cache: 'yarn' 25 | 26 | - name: Install 27 | uses: dtolnay/rust-toolchain@stable 28 | with: 29 | components: clippy, rustfmt 30 | 31 | - name: Install dependencies 32 | run: yarn install 33 | 34 | - name: ESLint 35 | run: yarn lint 36 | 37 | - name: Cargo fmt 38 | run: cargo fmt -- --check 39 | 40 | - name: Clippy 41 | run: cargo clippy 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/node 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=node 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | 80 | # parcel-bundler cache (https://parceljs.org/) 81 | .cache 82 | 83 | # Next.js build output 84 | .next 85 | 86 | # Nuxt.js build / generate output 87 | .nuxt 88 | dist 89 | 90 | # Gatsby files 91 | .cache/ 92 | # Comment in the public line in if your project uses Gatsby and not Next.js 93 | # https://nextjs.org/blog/next-9-1#public-directory-support 94 | # public 95 | 96 | # vuepress build output 97 | .vuepress/dist 98 | 99 | # Serverless directories 100 | .serverless/ 101 | 102 | # FuseBox cache 103 | .fusebox/ 104 | 105 | # DynamoDB Local files 106 | .dynamodb/ 107 | 108 | # TernJS port file 109 | .tern-port 110 | 111 | # Stores VSCode versions used for testing VSCode extensions 112 | .vscode-test 113 | 114 | # End of https://www.toptal.com/developers/gitignore/api/node 115 | 116 | 117 | #Added by cargo 118 | 119 | /target 120 | Cargo.lock 121 | 122 | *.node 123 | .pnp.* 124 | .yarn/* 125 | !.yarn/patches 126 | !.yarn/plugins 127 | !.yarn/releases 128 | !.yarn/sdks 129 | !.yarn/versions -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | target 2 | .yarn -------------------------------------------------------------------------------- /.taplo.toml: -------------------------------------------------------------------------------- 1 | exclude = ["node_modules/**/*.toml"] 2 | 3 | # https://taplo.tamasfe.dev/configuration/formatter-options.html 4 | [formatting] 5 | align_entries = true 6 | indent_tables = true 7 | reorder_keys = true 8 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | npmAuditRegistry: 'https://registry.npmjs.org' 4 | 5 | yarnPath: .yarn/releases/yarn-3.4.1.cjs 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["nemurubaka "] 3 | edition = "2021" 4 | name = "thumbhash-node" 5 | version = "0.1.0" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [lib] 10 | crate-type = ["cdylib"] 11 | 12 | [target.'cfg(not(target_os = "linux"))'.dependencies] 13 | mimalloc-rust = "0.2" 14 | 15 | [target.'cfg(all(target_os = "linux", not(all(target_env = "musl", target_arch = "aarch64"))))'.dependencies] 16 | mimalloc-rust = { version = "0.2", features = ["local-dynamic-tls"] } 17 | 18 | [dependencies] 19 | napi = "2" 20 | napi-derive = "2" 21 | thumbhash = "0.1.0" 22 | 23 | [build-dependencies] 24 | napi-build = "2" 25 | 26 | [profile.release] 27 | opt-level = 3 28 | lto = true 29 | codegen-units = 1 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 nemurubaka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `thumbhash-node` 2 | 3 | ![https://github.com/amehashi/thumbhash-node/actions](https://github.com/amehashi/thumbhash-node/workflows/CI/badge.svg) 4 | 5 | [`thumbhash`](https://github.com/evanw/thumbhash) binding for Node.js. 6 | 7 | ## Install this package 8 | 9 | ``` 10 | yarn add thumbhash-node 11 | pnpm add thumbhash-node 12 | npm install thumbhash-node 13 | ``` 14 | 15 | ## Support matrix 16 | 17 | ### Operating Systems 18 | 19 | | | node14 | node16 | node18 | 20 | | ---------------- | ------ | ------ | ------ | 21 | | Windows x64 | ✓ | ✓ | ✓ | 22 | | Windows x32 | ✓ | ✓ | ✓ | 23 | | Windows arm64 | ✓ | ✓ | ✓ | 24 | | macOS x64 | ✓ | ✓ | ✓ | 25 | | macOS arm64 | ✓ | ✓ | ✓ | 26 | | Linux x64 gnu | ✓ | ✓ | ✓ | 27 | | Linux x64 musl | ✓ | ✓ | ✓ | 28 | | Linux arm gnu | ✓ | ✓ | ✓ | 29 | | Linux arm64 gnu | ✓ | ✓ | ✓ | 30 | | Linux arm64 musl | ✓ | ✓ | ✓ | 31 | | Android arm64 | ✓ | ✓ | ✓ | 32 | | Android armv7 | ✓ | ✓ | ✓ | 33 | | FreeBSD x64 | ✓ | ✓ | ✓ | 34 | 35 | ## API 36 | 37 | ```ts 38 | /** Encodes an RGBA image to a ThumbHash. RGB should not be premultiplied by A. */ 39 | export function rgbaToThumbHash(w: number, h: number, rgba: Uint8Array): Uint8Array 40 | /** 41 | * Extracts the approximate aspect ratio of the original image. 42 | * An error will be returned if the input is too short. 43 | */ 44 | export function thumbHashToApproximateAspectRatio(hash: Uint8Array): number 45 | export interface Rgba { 46 | r: number 47 | g: number 48 | b: number 49 | a: number 50 | } 51 | /** 52 | * Extracts the average color from a ThumbHash. 53 | * Returns the RGBA values where each value ranges from 0 to 1. 54 | * RGB is not be premultiplied by A. 55 | * An error will be returned if the input is too short. 56 | */ 57 | export function thumbHashToAverageRGBA(hash: Uint8Array): Rgba 58 | export interface Image { 59 | width: number 60 | height: number 61 | rgba: Uint8Array 62 | } 63 | /** 64 | * Decodes a ThumbHash to an RGBA image. 65 | * RGB is not be premultiplied by A. 66 | * Returns the width, height, and pixels of the rendered placeholder image. 67 | * An error will be returned if the input is too short. 68 | */ 69 | export function thumbHashToRGBA(hash: Uint8Array): Image 70 | ``` 71 | -------------------------------------------------------------------------------- /__test__/index.spec.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | test('rgba to thumbhash', (t) => { 4 | t.is(true, true) 5 | }) 6 | -------------------------------------------------------------------------------- /benchmark/bench.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path' 2 | import { cwd } from 'process' 3 | 4 | import { loadImage, createCanvas } from '@napi-rs/canvas' 5 | import b from 'benny' 6 | 7 | import { rgbaToThumbHash as rgbaToThumbHashNode } from '../index.js' 8 | 9 | async function loadImageAndConvertToRgba() { 10 | const maxSize = 100 11 | const imgPath = join(cwd(), './benchmark/fixture/img.png') 12 | const image = await loadImage(imgPath) 13 | const width = image.width 14 | const height = image.height 15 | 16 | const scale = Math.min(maxSize / width, maxSize / height) 17 | const resizedWidth = Math.round(width * scale) 18 | const resizedHeight = Math.round(height * scale) 19 | 20 | const canvas = createCanvas(resizedWidth, resizedHeight) 21 | const ctx = canvas.getContext('2d') 22 | ctx.drawImage(image, 0, 0, canvas.width, canvas.height) 23 | 24 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height) 25 | const rgba = new Uint8Array(imageData.data.buffer) 26 | 27 | return { 28 | height: imageData.height, 29 | width: imageData.width, 30 | rgba, 31 | } 32 | } 33 | 34 | async function run() { 35 | const { height, width, rgba } = await loadImageAndConvertToRgba() 36 | 37 | await b.suite( 38 | 'to thumbhash', 39 | 40 | b.add('thumbhash-node', () => { 41 | rgbaToThumbHashNode(width, height, rgba) 42 | }), 43 | 44 | b.cycle(), 45 | b.complete(), 46 | ) 47 | } 48 | 49 | run().catch((e) => { 50 | console.error(e) 51 | }) 52 | -------------------------------------------------------------------------------- /benchmark/fixture/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amehashi/thumbhash-node/4b2da506139c0e260ab82cb7b85930781178c642/benchmark/fixture/img.png -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate napi_build; 2 | 3 | fn main() { 4 | napi_build::setup(); 5 | } 6 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | /* auto-generated by NAPI-RS */ 5 | 6 | /** Encodes an RGBA image to a ThumbHash. RGB should not be premultiplied by A. */ 7 | export function rgbaToThumbHash(w: number, h: number, rgba: Uint8Array): Uint8Array 8 | /** 9 | * Extracts the approximate aspect ratio of the original image. 10 | * An error will be returned if the input is too short. 11 | */ 12 | export function thumbHashToApproximateAspectRatio(hash: Uint8Array): number 13 | export interface Rgba { 14 | r: number 15 | g: number 16 | b: number 17 | a: number 18 | } 19 | /** 20 | * Extracts the average color from a ThumbHash. 21 | * Returns the RGBA values where each value ranges from 0 to 1. 22 | * RGB is not be premultiplied by A. 23 | * An error will be returned if the input is too short. 24 | */ 25 | export function thumbHashToAverageRGBA(hash: Uint8Array): Rgba 26 | export interface Image { 27 | width: number 28 | height: number 29 | rgba: Uint8Array 30 | } 31 | /** 32 | * Decodes a ThumbHash to an RGBA image. 33 | * RGB is not be premultiplied by A. 34 | * Returns the width, height, and pixels of the rendered placeholder image. 35 | * An error will be returned if the input is too short. 36 | */ 37 | export function thumbHashToRGBA(hash: Uint8Array): Image 38 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { existsSync, readFileSync } = require('fs') 2 | const { join } = require('path') 3 | 4 | const { platform, arch } = process 5 | 6 | let nativeBinding = null 7 | let localFileExisted = false 8 | let loadError = null 9 | 10 | function isMusl() { 11 | // For Node 10 12 | if (!process.report || typeof process.report.getReport !== 'function') { 13 | try { 14 | const lddPath = require('child_process').execSync('which ldd').toString().trim() 15 | return readFileSync(lddPath, 'utf8').includes('musl') 16 | } catch (e) { 17 | return true 18 | } 19 | } else { 20 | const { glibcVersionRuntime } = process.report.getReport().header 21 | return !glibcVersionRuntime 22 | } 23 | } 24 | 25 | switch (platform) { 26 | case 'android': 27 | switch (arch) { 28 | case 'arm64': 29 | localFileExisted = existsSync(join(__dirname, 'thumbhash-node.android-arm64.node')) 30 | try { 31 | if (localFileExisted) { 32 | nativeBinding = require('./thumbhash-node.android-arm64.node') 33 | } else { 34 | nativeBinding = require('thumbhash-node-android-arm64') 35 | } 36 | } catch (e) { 37 | loadError = e 38 | } 39 | break 40 | case 'arm': 41 | localFileExisted = existsSync(join(__dirname, 'thumbhash-node.android-arm-eabi.node')) 42 | try { 43 | if (localFileExisted) { 44 | nativeBinding = require('./thumbhash-node.android-arm-eabi.node') 45 | } else { 46 | nativeBinding = require('thumbhash-node-android-arm-eabi') 47 | } 48 | } catch (e) { 49 | loadError = e 50 | } 51 | break 52 | default: 53 | throw new Error(`Unsupported architecture on Android ${arch}`) 54 | } 55 | break 56 | case 'win32': 57 | switch (arch) { 58 | case 'x64': 59 | localFileExisted = existsSync(join(__dirname, 'thumbhash-node.win32-x64-msvc.node')) 60 | try { 61 | if (localFileExisted) { 62 | nativeBinding = require('./thumbhash-node.win32-x64-msvc.node') 63 | } else { 64 | nativeBinding = require('thumbhash-node-win32-x64-msvc') 65 | } 66 | } catch (e) { 67 | loadError = e 68 | } 69 | break 70 | case 'ia32': 71 | localFileExisted = existsSync(join(__dirname, 'thumbhash-node.win32-ia32-msvc.node')) 72 | try { 73 | if (localFileExisted) { 74 | nativeBinding = require('./thumbhash-node.win32-ia32-msvc.node') 75 | } else { 76 | nativeBinding = require('thumbhash-node-win32-ia32-msvc') 77 | } 78 | } catch (e) { 79 | loadError = e 80 | } 81 | break 82 | case 'arm64': 83 | localFileExisted = existsSync(join(__dirname, 'thumbhash-node.win32-arm64-msvc.node')) 84 | try { 85 | if (localFileExisted) { 86 | nativeBinding = require('./thumbhash-node.win32-arm64-msvc.node') 87 | } else { 88 | nativeBinding = require('thumbhash-node-win32-arm64-msvc') 89 | } 90 | } catch (e) { 91 | loadError = e 92 | } 93 | break 94 | default: 95 | throw new Error(`Unsupported architecture on Windows: ${arch}`) 96 | } 97 | break 98 | case 'darwin': 99 | localFileExisted = existsSync(join(__dirname, 'thumbhash-node.darwin-universal.node')) 100 | try { 101 | if (localFileExisted) { 102 | nativeBinding = require('./thumbhash-node.darwin-universal.node') 103 | } else { 104 | nativeBinding = require('thumbhash-node-darwin-universal') 105 | } 106 | break 107 | } catch {} 108 | switch (arch) { 109 | case 'x64': 110 | localFileExisted = existsSync(join(__dirname, 'thumbhash-node.darwin-x64.node')) 111 | try { 112 | if (localFileExisted) { 113 | nativeBinding = require('./thumbhash-node.darwin-x64.node') 114 | } else { 115 | nativeBinding = require('thumbhash-node-darwin-x64') 116 | } 117 | } catch (e) { 118 | loadError = e 119 | } 120 | break 121 | case 'arm64': 122 | localFileExisted = existsSync(join(__dirname, 'thumbhash-node.darwin-arm64.node')) 123 | try { 124 | if (localFileExisted) { 125 | nativeBinding = require('./thumbhash-node.darwin-arm64.node') 126 | } else { 127 | nativeBinding = require('thumbhash-node-darwin-arm64') 128 | } 129 | } catch (e) { 130 | loadError = e 131 | } 132 | break 133 | default: 134 | throw new Error(`Unsupported architecture on macOS: ${arch}`) 135 | } 136 | break 137 | case 'freebsd': 138 | if (arch !== 'x64') { 139 | throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) 140 | } 141 | localFileExisted = existsSync(join(__dirname, 'thumbhash-node.freebsd-x64.node')) 142 | try { 143 | if (localFileExisted) { 144 | nativeBinding = require('./thumbhash-node.freebsd-x64.node') 145 | } else { 146 | nativeBinding = require('thumbhash-node-freebsd-x64') 147 | } 148 | } catch (e) { 149 | loadError = e 150 | } 151 | break 152 | case 'linux': 153 | switch (arch) { 154 | case 'x64': 155 | if (isMusl()) { 156 | localFileExisted = existsSync(join(__dirname, 'thumbhash-node.linux-x64-musl.node')) 157 | try { 158 | if (localFileExisted) { 159 | nativeBinding = require('./thumbhash-node.linux-x64-musl.node') 160 | } else { 161 | nativeBinding = require('thumbhash-node-linux-x64-musl') 162 | } 163 | } catch (e) { 164 | loadError = e 165 | } 166 | } else { 167 | localFileExisted = existsSync(join(__dirname, 'thumbhash-node.linux-x64-gnu.node')) 168 | try { 169 | if (localFileExisted) { 170 | nativeBinding = require('./thumbhash-node.linux-x64-gnu.node') 171 | } else { 172 | nativeBinding = require('thumbhash-node-linux-x64-gnu') 173 | } 174 | } catch (e) { 175 | loadError = e 176 | } 177 | } 178 | break 179 | case 'arm64': 180 | if (isMusl()) { 181 | localFileExisted = existsSync(join(__dirname, 'thumbhash-node.linux-arm64-musl.node')) 182 | try { 183 | if (localFileExisted) { 184 | nativeBinding = require('./thumbhash-node.linux-arm64-musl.node') 185 | } else { 186 | nativeBinding = require('thumbhash-node-linux-arm64-musl') 187 | } 188 | } catch (e) { 189 | loadError = e 190 | } 191 | } else { 192 | localFileExisted = existsSync(join(__dirname, 'thumbhash-node.linux-arm64-gnu.node')) 193 | try { 194 | if (localFileExisted) { 195 | nativeBinding = require('./thumbhash-node.linux-arm64-gnu.node') 196 | } else { 197 | nativeBinding = require('thumbhash-node-linux-arm64-gnu') 198 | } 199 | } catch (e) { 200 | loadError = e 201 | } 202 | } 203 | break 204 | case 'arm': 205 | localFileExisted = existsSync(join(__dirname, 'thumbhash-node.linux-arm-gnueabihf.node')) 206 | try { 207 | if (localFileExisted) { 208 | nativeBinding = require('./thumbhash-node.linux-arm-gnueabihf.node') 209 | } else { 210 | nativeBinding = require('thumbhash-node-linux-arm-gnueabihf') 211 | } 212 | } catch (e) { 213 | loadError = e 214 | } 215 | break 216 | default: 217 | throw new Error(`Unsupported architecture on Linux: ${arch}`) 218 | } 219 | break 220 | default: 221 | throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) 222 | } 223 | 224 | if (!nativeBinding) { 225 | if (loadError) { 226 | throw loadError 227 | } 228 | throw new Error(`Failed to load native binding`) 229 | } 230 | 231 | const { rgbaToThumbHash, thumbHashToApproximateAspectRatio, thumbHashToAverageRGBA, thumbHashToRGBA } = nativeBinding 232 | 233 | module.exports.rgbaToThumbHash = rgbaToThumbHash 234 | module.exports.thumbHashToApproximateAspectRatio = thumbHashToApproximateAspectRatio 235 | module.exports.thumbHashToAverageRGBA = thumbHashToAverageRGBA 236 | module.exports.thumbHashToRGBA = thumbHashToRGBA 237 | -------------------------------------------------------------------------------- /npm/android-arm-eabi/README.md: -------------------------------------------------------------------------------- 1 | # `thumbhash-node-android-arm-eabi` 2 | 3 | This is the **armv7-linux-androideabi** binary for `thumbhash-node` 4 | -------------------------------------------------------------------------------- /npm/android-arm-eabi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thumbhash-node-android-arm-eabi", 3 | "version": "0.1.3", 4 | "os": [ 5 | "android" 6 | ], 7 | "cpu": [ 8 | "arm" 9 | ], 10 | "main": "thumbhash-node.android-arm-eabi.node", 11 | "files": [ 12 | "thumbhash-node.android-arm-eabi.node" 13 | ], 14 | "description": "thumbhash binding for Node.js", 15 | "keywords": [ 16 | "napi-rs", 17 | "NAPI", 18 | "N-API", 19 | "Rust", 20 | "node-addon", 21 | "node-addon-api" 22 | ], 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">= 10" 26 | }, 27 | "publishConfig": { 28 | "registry": "https://registry.npmjs.org/", 29 | "access": "public" 30 | }, 31 | "repository": "https://github.com/amehashi/thumbhash-node" 32 | } -------------------------------------------------------------------------------- /npm/android-arm64/README.md: -------------------------------------------------------------------------------- 1 | # `thumbhash-node-android-arm64` 2 | 3 | This is the **aarch64-linux-android** binary for `thumbhash-node` 4 | -------------------------------------------------------------------------------- /npm/android-arm64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thumbhash-node-android-arm64", 3 | "version": "0.1.3", 4 | "os": [ 5 | "android" 6 | ], 7 | "cpu": [ 8 | "arm64" 9 | ], 10 | "main": "thumbhash-node.android-arm64.node", 11 | "files": [ 12 | "thumbhash-node.android-arm64.node" 13 | ], 14 | "description": "thumbhash binding for Node.js", 15 | "keywords": [ 16 | "napi-rs", 17 | "NAPI", 18 | "N-API", 19 | "Rust", 20 | "node-addon", 21 | "node-addon-api" 22 | ], 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">= 10" 26 | }, 27 | "publishConfig": { 28 | "registry": "https://registry.npmjs.org/", 29 | "access": "public" 30 | }, 31 | "repository": "https://github.com/amehashi/thumbhash-node" 32 | } -------------------------------------------------------------------------------- /npm/darwin-arm64/README.md: -------------------------------------------------------------------------------- 1 | # `thumbhash-node-darwin-arm64` 2 | 3 | This is the **aarch64-apple-darwin** binary for `thumbhash-node` 4 | -------------------------------------------------------------------------------- /npm/darwin-arm64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thumbhash-node-darwin-arm64", 3 | "version": "0.1.3", 4 | "os": [ 5 | "darwin" 6 | ], 7 | "cpu": [ 8 | "arm64" 9 | ], 10 | "main": "thumbhash-node.darwin-arm64.node", 11 | "files": [ 12 | "thumbhash-node.darwin-arm64.node" 13 | ], 14 | "description": "thumbhash binding for Node.js", 15 | "keywords": [ 16 | "napi-rs", 17 | "NAPI", 18 | "N-API", 19 | "Rust", 20 | "node-addon", 21 | "node-addon-api" 22 | ], 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">= 10" 26 | }, 27 | "publishConfig": { 28 | "registry": "https://registry.npmjs.org/", 29 | "access": "public" 30 | }, 31 | "repository": "https://github.com/amehashi/thumbhash-node" 32 | } -------------------------------------------------------------------------------- /npm/darwin-x64/README.md: -------------------------------------------------------------------------------- 1 | # `thumbhash-node-darwin-x64` 2 | 3 | This is the **x86_64-apple-darwin** binary for `thumbhash-node` 4 | -------------------------------------------------------------------------------- /npm/darwin-x64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thumbhash-node-darwin-x64", 3 | "version": "0.1.3", 4 | "os": [ 5 | "darwin" 6 | ], 7 | "cpu": [ 8 | "x64" 9 | ], 10 | "main": "thumbhash-node.darwin-x64.node", 11 | "files": [ 12 | "thumbhash-node.darwin-x64.node" 13 | ], 14 | "description": "thumbhash binding for Node.js", 15 | "keywords": [ 16 | "napi-rs", 17 | "NAPI", 18 | "N-API", 19 | "Rust", 20 | "node-addon", 21 | "node-addon-api" 22 | ], 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">= 10" 26 | }, 27 | "publishConfig": { 28 | "registry": "https://registry.npmjs.org/", 29 | "access": "public" 30 | }, 31 | "repository": "https://github.com/amehashi/thumbhash-node" 32 | } -------------------------------------------------------------------------------- /npm/freebsd-x64/README.md: -------------------------------------------------------------------------------- 1 | # `thumbhash-node-freebsd-x64` 2 | 3 | This is the **x86_64-unknown-freebsd** binary for `thumbhash-node` 4 | -------------------------------------------------------------------------------- /npm/freebsd-x64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thumbhash-node-freebsd-x64", 3 | "version": "0.1.3", 4 | "os": [ 5 | "freebsd" 6 | ], 7 | "cpu": [ 8 | "x64" 9 | ], 10 | "main": "thumbhash-node.freebsd-x64.node", 11 | "files": [ 12 | "thumbhash-node.freebsd-x64.node" 13 | ], 14 | "description": "thumbhash binding for Node.js", 15 | "keywords": [ 16 | "napi-rs", 17 | "NAPI", 18 | "N-API", 19 | "Rust", 20 | "node-addon", 21 | "node-addon-api" 22 | ], 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">= 10" 26 | }, 27 | "publishConfig": { 28 | "registry": "https://registry.npmjs.org/", 29 | "access": "public" 30 | }, 31 | "repository": "https://github.com/amehashi/thumbhash-node" 32 | } -------------------------------------------------------------------------------- /npm/linux-arm-gnueabihf/README.md: -------------------------------------------------------------------------------- 1 | # `thumbhash-node-linux-arm-gnueabihf` 2 | 3 | This is the **armv7-unknown-linux-gnueabihf** binary for `thumbhash-node` 4 | -------------------------------------------------------------------------------- /npm/linux-arm-gnueabihf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thumbhash-node-linux-arm-gnueabihf", 3 | "version": "0.1.3", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "arm" 9 | ], 10 | "main": "thumbhash-node.linux-arm-gnueabihf.node", 11 | "files": [ 12 | "thumbhash-node.linux-arm-gnueabihf.node" 13 | ], 14 | "description": "thumbhash binding for Node.js", 15 | "keywords": [ 16 | "napi-rs", 17 | "NAPI", 18 | "N-API", 19 | "Rust", 20 | "node-addon", 21 | "node-addon-api" 22 | ], 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">= 10" 26 | }, 27 | "publishConfig": { 28 | "registry": "https://registry.npmjs.org/", 29 | "access": "public" 30 | }, 31 | "repository": "https://github.com/amehashi/thumbhash-node" 32 | } -------------------------------------------------------------------------------- /npm/linux-arm64-gnu/README.md: -------------------------------------------------------------------------------- 1 | # `thumbhash-node-linux-arm64-gnu` 2 | 3 | This is the **aarch64-unknown-linux-gnu** binary for `thumbhash-node` 4 | -------------------------------------------------------------------------------- /npm/linux-arm64-gnu/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thumbhash-node-linux-arm64-gnu", 3 | "version": "0.1.3", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "arm64" 9 | ], 10 | "main": "thumbhash-node.linux-arm64-gnu.node", 11 | "files": [ 12 | "thumbhash-node.linux-arm64-gnu.node" 13 | ], 14 | "description": "thumbhash binding for Node.js", 15 | "keywords": [ 16 | "napi-rs", 17 | "NAPI", 18 | "N-API", 19 | "Rust", 20 | "node-addon", 21 | "node-addon-api" 22 | ], 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">= 10" 26 | }, 27 | "publishConfig": { 28 | "registry": "https://registry.npmjs.org/", 29 | "access": "public" 30 | }, 31 | "repository": "https://github.com/amehashi/thumbhash-node", 32 | "libc": [ 33 | "glibc" 34 | ] 35 | } -------------------------------------------------------------------------------- /npm/linux-arm64-musl/README.md: -------------------------------------------------------------------------------- 1 | # `thumbhash-node-linux-arm64-musl` 2 | 3 | This is the **aarch64-unknown-linux-musl** binary for `thumbhash-node` 4 | -------------------------------------------------------------------------------- /npm/linux-arm64-musl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thumbhash-node-linux-arm64-musl", 3 | "version": "0.1.3", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "arm64" 9 | ], 10 | "main": "thumbhash-node.linux-arm64-musl.node", 11 | "files": [ 12 | "thumbhash-node.linux-arm64-musl.node" 13 | ], 14 | "description": "thumbhash binding for Node.js", 15 | "keywords": [ 16 | "napi-rs", 17 | "NAPI", 18 | "N-API", 19 | "Rust", 20 | "node-addon", 21 | "node-addon-api" 22 | ], 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">= 10" 26 | }, 27 | "publishConfig": { 28 | "registry": "https://registry.npmjs.org/", 29 | "access": "public" 30 | }, 31 | "repository": "https://github.com/amehashi/thumbhash-node", 32 | "libc": [ 33 | "musl" 34 | ] 35 | } -------------------------------------------------------------------------------- /npm/linux-x64-gnu/README.md: -------------------------------------------------------------------------------- 1 | # `thumbhash-node-linux-x64-gnu` 2 | 3 | This is the **x86_64-unknown-linux-gnu** binary for `thumbhash-node` 4 | -------------------------------------------------------------------------------- /npm/linux-x64-gnu/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thumbhash-node-linux-x64-gnu", 3 | "version": "0.1.3", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "x64" 9 | ], 10 | "main": "thumbhash-node.linux-x64-gnu.node", 11 | "files": [ 12 | "thumbhash-node.linux-x64-gnu.node" 13 | ], 14 | "description": "thumbhash binding for Node.js", 15 | "keywords": [ 16 | "napi-rs", 17 | "NAPI", 18 | "N-API", 19 | "Rust", 20 | "node-addon", 21 | "node-addon-api" 22 | ], 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">= 10" 26 | }, 27 | "publishConfig": { 28 | "registry": "https://registry.npmjs.org/", 29 | "access": "public" 30 | }, 31 | "repository": "https://github.com/amehashi/thumbhash-node", 32 | "libc": [ 33 | "glibc" 34 | ] 35 | } -------------------------------------------------------------------------------- /npm/linux-x64-musl/README.md: -------------------------------------------------------------------------------- 1 | # `thumbhash-node-linux-x64-musl` 2 | 3 | This is the **x86_64-unknown-linux-musl** binary for `thumbhash-node` 4 | -------------------------------------------------------------------------------- /npm/linux-x64-musl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thumbhash-node-linux-x64-musl", 3 | "version": "0.1.3", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "x64" 9 | ], 10 | "main": "thumbhash-node.linux-x64-musl.node", 11 | "files": [ 12 | "thumbhash-node.linux-x64-musl.node" 13 | ], 14 | "description": "thumbhash binding for Node.js", 15 | "keywords": [ 16 | "napi-rs", 17 | "NAPI", 18 | "N-API", 19 | "Rust", 20 | "node-addon", 21 | "node-addon-api" 22 | ], 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">= 10" 26 | }, 27 | "publishConfig": { 28 | "registry": "https://registry.npmjs.org/", 29 | "access": "public" 30 | }, 31 | "repository": "https://github.com/amehashi/thumbhash-node", 32 | "libc": [ 33 | "musl" 34 | ] 35 | } -------------------------------------------------------------------------------- /npm/win32-arm64-msvc/README.md: -------------------------------------------------------------------------------- 1 | # `thumbhash-node-win32-arm64-msvc` 2 | 3 | This is the **aarch64-pc-windows-msvc** binary for `thumbhash-node` 4 | -------------------------------------------------------------------------------- /npm/win32-arm64-msvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thumbhash-node-win32-arm64-msvc", 3 | "version": "0.1.3", 4 | "os": [ 5 | "win32" 6 | ], 7 | "cpu": [ 8 | "arm64" 9 | ], 10 | "main": "thumbhash-node.win32-arm64-msvc.node", 11 | "files": [ 12 | "thumbhash-node.win32-arm64-msvc.node" 13 | ], 14 | "description": "thumbhash binding for Node.js", 15 | "keywords": [ 16 | "napi-rs", 17 | "NAPI", 18 | "N-API", 19 | "Rust", 20 | "node-addon", 21 | "node-addon-api" 22 | ], 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">= 10" 26 | }, 27 | "publishConfig": { 28 | "registry": "https://registry.npmjs.org/", 29 | "access": "public" 30 | }, 31 | "repository": "https://github.com/amehashi/thumbhash-node" 32 | } -------------------------------------------------------------------------------- /npm/win32-ia32-msvc/README.md: -------------------------------------------------------------------------------- 1 | # `thumbhash-node-win32-ia32-msvc` 2 | 3 | This is the **i686-pc-windows-msvc** binary for `thumbhash-node` 4 | -------------------------------------------------------------------------------- /npm/win32-ia32-msvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thumbhash-node-win32-ia32-msvc", 3 | "version": "0.1.3", 4 | "os": [ 5 | "win32" 6 | ], 7 | "cpu": [ 8 | "ia32" 9 | ], 10 | "main": "thumbhash-node.win32-ia32-msvc.node", 11 | "files": [ 12 | "thumbhash-node.win32-ia32-msvc.node" 13 | ], 14 | "description": "thumbhash binding for Node.js", 15 | "keywords": [ 16 | "napi-rs", 17 | "NAPI", 18 | "N-API", 19 | "Rust", 20 | "node-addon", 21 | "node-addon-api" 22 | ], 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">= 10" 26 | }, 27 | "publishConfig": { 28 | "registry": "https://registry.npmjs.org/", 29 | "access": "public" 30 | }, 31 | "repository": "https://github.com/amehashi/thumbhash-node" 32 | } -------------------------------------------------------------------------------- /npm/win32-x64-msvc/README.md: -------------------------------------------------------------------------------- 1 | # `thumbhash-node-win32-x64-msvc` 2 | 3 | This is the **x86_64-pc-windows-msvc** binary for `thumbhash-node` 4 | -------------------------------------------------------------------------------- /npm/win32-x64-msvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thumbhash-node-win32-x64-msvc", 3 | "version": "0.1.3", 4 | "os": [ 5 | "win32" 6 | ], 7 | "cpu": [ 8 | "x64" 9 | ], 10 | "main": "thumbhash-node.win32-x64-msvc.node", 11 | "files": [ 12 | "thumbhash-node.win32-x64-msvc.node" 13 | ], 14 | "description": "thumbhash binding for Node.js", 15 | "keywords": [ 16 | "napi-rs", 17 | "NAPI", 18 | "N-API", 19 | "Rust", 20 | "node-addon", 21 | "node-addon-api" 22 | ], 23 | "license": "MIT", 24 | "engines": { 25 | "node": ">= 10" 26 | }, 27 | "publishConfig": { 28 | "registry": "https://registry.npmjs.org/", 29 | "access": "public" 30 | }, 31 | "repository": "https://github.com/amehashi/thumbhash-node" 32 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thumbhash-node", 3 | "version": "0.1.3", 4 | "description": "thumbhash binding for Node.js", 5 | "author": "nemurubaka ", 6 | "main": "index.js", 7 | "repository": "https://github.com/amehashi/thumbhash-node", 8 | "license": "MIT", 9 | "keywords": [ 10 | "thumbhash", 11 | "image placeholder", 12 | "image", 13 | "napi-rs", 14 | "NAPI", 15 | "N-API", 16 | "Rust", 17 | "node-addon", 18 | "node-addon-api" 19 | ], 20 | "files": [ 21 | "index.d.ts", 22 | "index.js" 23 | ], 24 | "napi": { 25 | "name": "thumbhash-node", 26 | "triples": { 27 | "defaults": true, 28 | "additional": [ 29 | "x86_64-unknown-linux-musl", 30 | "aarch64-unknown-linux-gnu", 31 | "i686-pc-windows-msvc", 32 | "armv7-unknown-linux-gnueabihf", 33 | "aarch64-apple-darwin", 34 | "aarch64-linux-android", 35 | "x86_64-unknown-freebsd", 36 | "aarch64-unknown-linux-musl", 37 | "aarch64-pc-windows-msvc", 38 | "armv7-linux-androideabi" 39 | ] 40 | } 41 | }, 42 | "engines": { 43 | "node": ">= 10" 44 | }, 45 | "publishConfig": { 46 | "registry": "https://registry.npmjs.org/", 47 | "access": "public" 48 | }, 49 | "scripts": { 50 | "artifacts": "napi artifacts", 51 | "simple": "node ./simple-test.js", 52 | "bench": "node -r @swc-node/register benchmark/bench.ts", 53 | "build": "napi build --platform --release --pipe \"prettier -w\"", 54 | "build:debug": "napi build --platform --pipe \"prettier -w\"", 55 | "format": "run-p format:prettier format:rs format:toml", 56 | "format:prettier": "prettier . -w", 57 | "format:toml": "taplo format", 58 | "format:rs": "cargo fmt", 59 | "lint": "eslint . -c ./.eslintrc.yml", 60 | "prepublishOnly": "napi prepublish -t npm", 61 | "test": "ava", 62 | "version": "napi version" 63 | }, 64 | "devDependencies": { 65 | "@napi-rs/canvas": "^0.1.39", 66 | "@napi-rs/cli": "^2.14.6", 67 | "@swc-node/register": "^1.5.5", 68 | "@swc/core": "^1.3.32", 69 | "@taplo/cli": "^0.5.2", 70 | "@types/node": "^18.15.9", 71 | "@typescript-eslint/eslint-plugin": "^5.50.0", 72 | "@typescript-eslint/parser": "^5.50.0", 73 | "ava": "^5.1.1", 74 | "benny": "^3.7.1", 75 | "chalk": "^5.2.0", 76 | "eslint": "^8.33.0", 77 | "eslint-config-prettier": "^8.6.0", 78 | "eslint-plugin-import": "^2.27.5", 79 | "eslint-plugin-prettier": "^4.2.1", 80 | "husky": "^8.0.3", 81 | "lint-staged": "^13.1.0", 82 | "npm-run-all": "^4.1.5", 83 | "prettier": "^2.8.3", 84 | "thumbhash": "^0.1.1", 85 | "typescript": "^4.9.5" 86 | }, 87 | "lint-staged": { 88 | "*.@(js|ts|tsx)": [ 89 | "eslint -c .eslintrc.yml --fix" 90 | ], 91 | "*.@(js|ts|tsx|yml|yaml|md|json)": [ 92 | "prettier --write" 93 | ], 94 | "*.toml": [ 95 | "taplo format" 96 | ] 97 | }, 98 | "ava": { 99 | "require": [ 100 | "@swc-node/register" 101 | ], 102 | "extensions": [ 103 | "ts" 104 | ], 105 | "timeout": "2m", 106 | "workerThreads": false, 107 | "environmentVariables": { 108 | "TS_NODE_PROJECT": "./tsconfig.json" 109 | } 110 | }, 111 | "prettier": { 112 | "printWidth": 120, 113 | "semi": false, 114 | "trailingComma": "all", 115 | "singleQuote": true, 116 | "arrowParens": "always" 117 | }, 118 | "packageManager": "yarn@3.4.1" 119 | } 120 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 2 | -------------------------------------------------------------------------------- /simple-test.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path') 2 | const { cwd } = require('process') 3 | 4 | const { loadImage, createCanvas } = require('@napi-rs/canvas') 5 | 6 | const { rgbaToThumbHash } = require('./index.js') 7 | 8 | async function loadImageAndConvertToRgba() { 9 | const maxSize = 100 10 | const imgPath = join(cwd(), './benchmark/fixture/img.png') 11 | const image = await loadImage(imgPath) 12 | const width = image.width 13 | const height = image.height 14 | 15 | const scale = Math.min(maxSize / width, maxSize / height) 16 | const resizedWidth = Math.round(width * scale) 17 | const resizedHeight = Math.round(height * scale) 18 | 19 | const canvas = createCanvas(resizedWidth, resizedHeight) 20 | const ctx = canvas.getContext('2d') 21 | ctx.drawImage(image, 0, 0, canvas.width, canvas.height) 22 | 23 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height) 24 | const rgba = new Uint8Array(imageData.data.buffer) 25 | 26 | return { 27 | height: imageData.height, 28 | width: imageData.width, 29 | rgba, 30 | } 31 | } 32 | 33 | const run = async () => { 34 | const { height, width, rgba } = await loadImageAndConvertToRgba() 35 | 36 | rgbaToThumbHash(width, height, rgba) 37 | } 38 | 39 | run() 40 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::all)] 2 | 3 | #[cfg(not(all(target_os = "linux", target_env = "musl", target_arch = "aarch64")))] 4 | #[global_allocator] 5 | static ALLOC: mimalloc_rust::GlobalMiMalloc = mimalloc_rust::GlobalMiMalloc; 6 | 7 | use napi::bindgen_prelude::*; 8 | use napi_derive::napi; 9 | use thumbhash::{ 10 | rgba_to_thumb_hash as rgba_to_thumb_hash_vendor, 11 | thumb_hash_to_approximate_aspect_ratio as thumb_hash_to_approximate_aspect_ratio_vendor, 12 | thumb_hash_to_average_rgba as thumb_hash_to_average_rgba_vendor, 13 | thumb_hash_to_rgba as thumb_hash_to_rgba_vendor, 14 | }; 15 | 16 | #[napi(js_name = "rgbaToThumbHash")] 17 | /// Encodes an RGBA image to a ThumbHash. RGB should not be premultiplied by A. 18 | pub fn rgba_to_thumb_hash(w: u32, h: u32, rgba: Uint8Array) -> Uint8Array { 19 | rgba_to_thumb_hash_vendor(w as usize, h as usize, &rgba).into() 20 | } 21 | 22 | #[napi(js_name = "thumbHashToApproximateAspectRatio")] 23 | /// Extracts the approximate aspect ratio of the original image. 24 | /// An error will be returned if the input is too short. 25 | pub fn thumb_hash_to_approximate_aspect_ratio(hash: Uint8Array) -> Result { 26 | thumb_hash_to_approximate_aspect_ratio_vendor(&hash) 27 | .map_err(|_| Error::new(Status::InvalidArg, String::from("invalid arg"))) 28 | } 29 | 30 | #[napi(object)] 31 | pub struct RGBA { 32 | pub r: f64, 33 | pub g: f64, 34 | pub b: f64, 35 | pub a: f64, 36 | } 37 | 38 | #[napi(js_name = "thumbHashToAverageRGBA")] 39 | /// Extracts the average color from a ThumbHash. 40 | /// Returns the RGBA values where each value ranges from 0 to 1. 41 | /// RGB is not be premultiplied by A. 42 | /// An error will be returned if the input is too short. 43 | pub fn thumb_hash_to_average_rgba(hash: Uint8Array) -> Result { 44 | match thumb_hash_to_average_rgba_vendor(&hash) { 45 | Ok((r, g, b, a)) => Ok(RGBA { 46 | r: r as f64, 47 | g: g as f64, 48 | b: b as f64, 49 | a: a as f64, 50 | }), 51 | Err(_) => Err(Error::new(Status::InvalidArg, String::from("invalid arg"))), 52 | } 53 | } 54 | 55 | #[napi(object)] 56 | pub struct Image { 57 | pub width: u32, 58 | pub height: u32, 59 | pub rgba: Uint8Array, 60 | } 61 | 62 | #[napi(js_name = "thumbHashToRGBA")] 63 | /// Decodes a ThumbHash to an RGBA image. 64 | /// RGB is not be premultiplied by A. 65 | /// Returns the width, height, and pixels of the rendered placeholder image. 66 | /// An error will be returned if the input is too short. 67 | pub fn thumb_hash_to_rgba(hash: Uint8Array) -> Result { 68 | match thumb_hash_to_rgba_vendor(&hash) { 69 | Ok((w, h, rgba)) => Ok(Image { 70 | width: w as u32, 71 | height: h as u32, 72 | rgba: rgba.into(), 73 | }), 74 | Err(_) => Err(Error::new(Status::InvalidArg, String::from("invalid arg"))), 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "strict": true, 5 | "moduleResolution": "node", 6 | "module": "CommonJS", 7 | "noUnusedLocals": true, 8 | "noUnusedParameters": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true 11 | }, 12 | "include": ["."], 13 | "exclude": ["node_modules"] 14 | } 15 | --------------------------------------------------------------------------------