This commit is contained in:
LongYinan 2022-01-07 00:48:08 +08:00
commit c0997adba5
No known key found for this signature in database
GPG Key ID: C3666B7FC82ADAD7
49 changed files with 3379 additions and 0 deletions

9
.cargo/config.toml Normal file
View File

@ -0,0 +1,9 @@
[target.x86_64-unknown-linux-musl]
rustflags = ["-C", "target-feature=+sse4.2", "-C", "target-feature=-crt-static"]
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
[target.aarch64-unknown-linux-musl]
linker = "aarch64-linux-musl-gcc"
rustflags = ["-C", "target-feature=-crt-static"]
[target.armv7-unknown-linux-gnueabihf]
linker = "arm-linux-gnueabihf-gcc"

13
.gitattributes vendored Normal file
View File

@ -0,0 +1,13 @@
# Auto detect text files and perform LF normalization
* text=auto
*.ts text eol=lf merge=union
*.tsx text eol=lf merge=union
*.rs text eol=lf merge=union
*.js text eol=lf merge=union
*.json text eol=lf merge=union
*.debug text eol=lf merge=union
index.js linguist-detectable=false
index.d.ts inguist-detectable=false

1
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1 @@
github: [Brooooooklyn]

462
.github/workflows/CI.yml vendored Normal file
View File

@ -0,0 +1,462 @@
name: CI
env:
DEBUG: napi:*
APP_NAME: image
MACOSX_DEPLOYMENT_TARGET: '10.13'
'on':
push:
branches:
- main
tags-ignore:
- '**'
paths-ignore:
- '**/*.md'
- LICENSE
- '**/*.gitignore'
- .editorconfig
- docs/**
pull_request: null
jobs:
build:
if: "!contains(github.event.head_commit.message, 'skip ci')"
strategy:
fail-fast: false
matrix:
settings:
- host: macos-latest
target: x86_64-apple-darwin
architecture: x64
build: |
yarn build
strip -x *.node
- host: windows-latest
build: |
yarn build
target: x86_64-pc-windows-msvc
architecture: x64
- host: windows-latest
build: |
yarn build --target i686-pc-windows-msvc
yarn test
target: i686-pc-windows-msvc
architecture: x86
- host: ubuntu-latest
target: x86_64-unknown-linux-gnu
architecture: x64
docker: |
docker pull $DOCKER_REGISTRY_URL/napi-rs/napi-rs/nodejs-rust:lts-debian
docker tag $DOCKER_REGISTRY_URL/napi-rs/napi-rs/nodejs-rust:lts-debian builder
build: |
docker run --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd):/build -w /build builder yarn build && strip *.node
- host: ubuntu-latest
target: x86_64-unknown-linux-musl
architecture: x64
docker: |
docker pull $DOCKER_REGISTRY_URL/napi-rs/napi-rs/nodejs-rust:lts-alpine
docker tag $DOCKER_REGISTRY_URL/napi-rs/napi-rs/nodejs-rust:lts-alpine builder
build: docker run --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd):/build -w /build builder sh -c "unset RUSTFLAGS && unset CC && yarn build && strip *.node"
- host: macos-latest
target: aarch64-apple-darwin
build: |
yarn build --target=aarch64-apple-darwin
strip -x *.node
- host: ubuntu-latest
architecture: x64
target: aarch64-unknown-linux-gnu
setup: |
sudo apt-get update
sudo apt-get install g++-aarch64-linux-gnu gcc-aarch64-linux-gnu -y
build: |
yarn build --target=aarch64-unknown-linux-gnu
aarch64-linux-gnu-strip *.node
- host: ubuntu-latest
architecture: x64
target: armv7-unknown-linux-gnueabihf
setup: |
sudo apt-get update
sudo apt-get install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf -y
build: |
yarn build --target=armv7-unknown-linux-gnueabihf
arm-linux-gnueabihf-strip *.node
- host: ubuntu-latest
architecture: x64
target: aarch64-linux-android
build: |
export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
export CC="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang"
export CXX="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android24-clang++"
export PATH="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin:${PATH}"
yarn build --target aarch64-linux-android
${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-strip *.node
- host: ubuntu-latest
architecture: x64
target: armv7-linux-androideabi
build: |
export CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang"
export CC="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang"
export CXX="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi24-clang++"
export PATH="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin:${PATH}"
yarn build --target armv7-linux-androideabi
${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/arm-linux-androideabi-strip *.node
- host: ubuntu-latest
architecture: x64
target: aarch64-unknown-linux-musl
downloadTarget: aarch64-unknown-linux-musl
docker: |
docker pull ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
docker tag ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine builder
build: |
docker run --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd):/build -w /build builder sh -c "yarn build --target=aarch64-unknown-linux-musl && /aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip *.node"
name: stable - ${{ matrix.settings.target }} - node@16
runs-on: ${{ matrix.settings.host }}
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: 16
check-latest: true
cache: yarn
architecture: ${{ matrix.settings.architecture }}
- name: Setup nasm
uses: ilammy/setup-nasm@v1
if: matrix.settings.target == 'x86_64-pc-windows-msvc' || matrix.settings.target == 'x86_64-apple-darwin'
- name: Install
uses: actions-rs/toolchain@v1
with:
profile: minimal
override: true
toolchain: stable
target: ${{ matrix.settings.target }}
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with:
command: generate-lockfile
- name: Cache cargo registry
uses: actions/cache@v2
with:
path: ~/.cargo/registry
key: ${{ matrix.settings.target }}-node@16-cargo-registry-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Cache cargo index
uses: actions/cache@v2
with:
path: ~/.cargo/git
key: ${{ matrix.settings.target }}-node@16-cargo-index-trimmed-${{ hashFiles('**/Cargo.lock') }}
- name: Cache NPM dependencies
uses: actions/cache@v2
with:
path: node_modules
key: npm-cache-${{ matrix.settings.target }}-node@16-${{ hashFiles('yarn.lock') }}
- name: Pull latest image
run: ${{ matrix.settings.docker }}
env:
DOCKER_REGISTRY_URL: ghcr.io
if: ${{ matrix.settings.docker }}
- name: Setup toolchain
run: ${{ matrix.settings.setup }}
if: ${{ matrix.settings.setup }}
shell: bash
- name: Install dependencies
run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
- name: Build
run: ${{ matrix.settings.build }}
shell: bash
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: bindings-${{ matrix.settings.target }}
path: ${{ env.APP_NAME }}.*.node
if-no-files-found: error
build-freebsd:
runs-on: macos-10.15
name: Build FreeBSD
steps:
- uses: actions/checkout@v2
- name: Build
id: build
uses: vmactions/freebsd-vm@v0.1.5
env:
DEBUG: napi:*
RUSTUP_HOME: /usr/local/rustup
CARGO_HOME: /usr/local/cargo
RUSTUP_IO_THREADS: 1
with:
envs: DEBUG RUSTUP_HOME CARGO_HOME RUSTUP_IO_THREADS
usesh: true
mem: 3000
prepare: |
pkg install -y curl node14 python2
curl -qL https://www.npmjs.com/install.sh | sh
npm install -g yarn
curl https://sh.rustup.rs -sSf --output rustup.sh
sh rustup.sh -y --profile minimal --default-toolchain stable
export PATH="/usr/local/cargo/bin:$PATH"
echo "~~~~ rustc --version ~~~~"
rustc --version
echo "~~~~ node -v ~~~~"
node -v
echo "~~~~ yarn --version ~~~~"
yarn --version
run: |
export PATH="/usr/local/cargo/bin:$PATH"
pwd
ls -lah
whoami
env
freebsd-version
yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
yarn build
strip -x *.node
yarn test
rm -rf node_modules
rm -rf target
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: bindings-freebsd
path: ${{ env.APP_NAME }}.*.node
if-no-files-found: error
test-macOS-windows-binding:
name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }}
needs:
- build
strategy:
fail-fast: false
matrix:
settings:
- host: windows-latest
target: x86_64-pc-windows-msvc
node:
- '14'
- '16'
- '17'
runs-on: ${{ matrix.settings.host }}
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
check-latest: true
cache: yarn
- name: Cache NPM dependencies
uses: actions/cache@v2
with:
path: node_modules
key: npm-cache-test-${{ matrix.settings.target }}-${{ matrix.node }}-${{ hashFiles('yarn.lock') }}
- name: Install dependencies
run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
- name: Download artifacts
uses: actions/download-artifact@v2
with:
name: bindings-${{ matrix.settings.target }}
path: .
- name: List packages
run: ls -R .
shell: bash
- name: Test bindings
run: yarn test
test-linux-x64-gnu-binding:
name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }}
needs:
- build
strategy:
fail-fast: false
matrix:
node:
- '14'
- '16'
- '17'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
check-latest: true
cache: yarn
- name: Cache NPM dependencies
uses: actions/cache@v2
with:
path: node_modules
key: npm-cache-test-linux-x64-gnu-${{ matrix.node }}-${{ hashFiles('yarn.lock') }}
- name: Install dependencies
run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
- name: Download artifacts
uses: actions/download-artifact@v2
with:
name: bindings-x86_64-unknown-linux-gnu
path: .
- name: List packages
run: ls -R .
shell: bash
- name: Test bindings
run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-slim yarn test
test-linux-x64-musl-binding:
name: Test bindings on x86_64-unknown-linux-musl - node@${{ matrix.node }}
needs:
- build
strategy:
fail-fast: false
matrix:
node:
- '14'
- '16'
- '17'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
check-latest: true
cache: yarn
- name: Cache NPM dependencies
uses: actions/cache@v2
with:
path: node_modules
key: npm-cache-test-x86_64-unknown-linux-musl-${{ matrix.node }}-${{ hashFiles('yarn.lock') }}
- name: Install dependencies
run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
- name: Download artifacts
uses: actions/download-artifact@v2
with:
name: bindings-x86_64-unknown-linux-musl
path: .
- name: List packages
run: ls -R .
shell: bash
- name: Test bindings
run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-alpine yarn test
test-linux-aarch64-gnu-binding:
name: Test bindings on aarch64-unknown-linux-gnu - node@${{ matrix.node }}
needs:
- build
strategy:
fail-fast: false
matrix:
node:
- '14'
- '16'
- '17'
runs-on: ubuntu-latest
steps:
- run: docker run --rm --privileged multiarch/qemu-user-static:register --reset
- uses: actions/checkout@v2
- name: Download artifacts
uses: actions/download-artifact@v2
with:
name: bindings-aarch64-unknown-linux-gnu
path: .
- name: List packages
run: ls -R .
shell: bash
- name: Cache NPM dependencies
uses: actions/cache@v2
with:
path: node_modules
key: npm-cache-test-linux-aarch64-gnu-${{ matrix.node }}-${{ hashFiles('yarn.lock') }}
- name: Install dependencies
run: yarn install --ignore-scripts --ignore-platform --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
- name: Setup and run tests
uses: addnab/docker-run-action@v3
with:
image: ghcr.io/napi-rs/napi-rs/nodejs:aarch64-${{ matrix.node }}
options: '-v ${{ github.workspace }}:/build -w /build'
run: |
set -e
yarn test
ls -la
test-linux-arm-gnueabihf-binding:
name: Test bindings on armv7-unknown-linux-gnueabihf - node@${{ matrix.node }}
needs:
- build
strategy:
fail-fast: false
matrix:
node:
- '14'
- '16'
- '17'
runs-on: ubuntu-latest
steps:
- run: docker run --rm --privileged multiarch/qemu-user-static:register --reset
- uses: actions/checkout@v2
- name: Download artifacts
uses: actions/download-artifact@v2
with:
name: bindings-armv7-unknown-linux-gnueabihf
path: .
- name: List packages
run: ls -R .
shell: bash
- name: Cache NPM dependencies
uses: actions/cache@v2
with:
path: node_modules
key: npm-cache-test-linux-arm-gnueabihf-${{ matrix.node }}-${{ hashFiles('yarn.lock') }}
- name: Install dependencies
run: yarn install --ignore-scripts --ignore-platform --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
- name: Setup and run tests
uses: addnab/docker-run-action@v3
with:
image: ghcr.io/napi-rs/napi-rs/nodejs:armhf-${{ matrix.node }}
options: '-v ${{ github.workspace }}:/build -w /build'
run: |
set -e
yarn test
ls -la
publish:
name: Publish
runs-on: ubuntu-latest
needs:
- build-freebsd
- test-macOS-windows-binding
- test-linux-x64-gnu-binding
- test-linux-x64-musl-binding
- test-linux-aarch64-gnu-binding
- test-linux-arm-gnueabihf-binding
steps:
- uses: actions/checkout@v2
- name: Setup node
uses: actions/setup-node@v2
with:
node-version: 16
check-latest: true
cache: yarn
- name: Cache NPM dependencies
uses: actions/cache@v2
with:
path: node_modules
key: npm-cache-ubuntu-latest-${{ hashFiles('yarn.lock') }}
restore-keys: |
npm-cache-
- name: Install dependencies
run: yarn install --ignore-scripts --frozen-lockfile --registry https://registry.npmjs.org --network-timeout 300000
- name: Download all artifacts
uses: actions/download-artifact@v2
with:
path: artifacts
- name: Move artifacts
run: yarn artifacts
- name: List packages
run: ls -R ./npm
shell: bash
- name: Publish
run: |
if git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+$";
then
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
npm publish --access public
elif git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+";
then
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
npm publish --tag next --access public
else
echo "Not a release, skipping publish"
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
target
node_modules
*.node
Cargo.lock
.swc
.DS_Store

10
.npmignore Normal file
View File

@ -0,0 +1,10 @@
target
Cargo.lock
.cargo
.github
npm
.eslintrc
.prettierignore
rustfmt.toml
yarn.lock
*.node

3
.prettierignore Normal file
View File

@ -0,0 +1,3 @@
target
node_modules
lib

35
Cargo.toml Normal file
View File

@ -0,0 +1,35 @@
[package]
edition = "2021"
name = "napi-rs_pngquant"
version = "0.0.0"
[lib]
crate-type = ["cdylib"]
[dependencies]
libc = "0.2"
napi = {version = "2", default-features = false, features = ["napi3"]}
napi-derive = {version = "2", default-features = false, features = ["type-def"]}
[target.'cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))'.dependencies.oxipng]
default-features = false
features = ["parallel", "libdeflater"]
version = "5"
[target.'cfg(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64"))'.dependencies.oxipng]
default-features = false
features = ["parallel"]
version = "5"
[target.'cfg(not(all(target_os = "linux", target_arch = "arm")))'.dependencies.mozjpeg-sys]
version = "1"
[target.'cfg(all(target_os = "linux", target_arch = "arm"))'.dependencies.mozjpeg-sys]
default-features = false
version = "1"
[build-dependencies]
napi-build = "1"
[profile.release]
lto = true

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-present LongYinan(github@lyn.one)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

91
README.md Normal file
View File

@ -0,0 +1,91 @@
# `@napi-rs/image`
Image processing library.
![CI](https://github.com/Brooooooklyn/image/workflows/CI/badge.svg)
[![install size](https://packagephobia.com/badge?p=@napi-rs/image)](https://packagephobia.com/result?p=@napi-rs/image)
[![Downloads](https://img.shields.io/npm/dm/@napi-rs/image.svg?sanitize=true)](https://npmcharts.com/compare/@napi-rs/image?minimal=true)
## Support matrix
| | node10 | node12 | node14 | node16 | node17 |
| --------------------- | ------ | ------ | ------ | ------ | ------ |
| Windows x64 | ✓ | ✓ | ✓ | ✓ | ✓ |
| Windows x32 | ✓ | ✓ | ✓ | ✓ | ✓ |
| macOS x64 | ✓ | ✓ | ✓ | ✓ | ✓ |
| macOS arm64 (m chips) | ✓ | ✓ | ✓ | ✓ | ✓ |
| Linux x64 gnu | ✓ | ✓ | ✓ | ✓ | ✓ |
| Linux x64 musl | ✓ | ✓ | ✓ | ✓ | ✓ |
| Linux arm gnu | ✓ | ✓ | ✓ | ✓ | ✓ |
| Linux arm64 gnu | ✓ | ✓ | ✓ | ✓ | ✓ |
| Linux arm64 musl | ✓ | ✓ | ✓ | ✓ | ✓ |
| Android arm64 | ✓ | ✓ | ✓ | ✓ | ✓ |
| Android armv7 | ✓ | ✓ | ✓ | ✓ | ✓ |
| FreeBSD x64 | ✓ | ✓ | ✓ | ✓ | ✓ |
## Lossless compression
### `PNG`
```ts
export interface PNGLosslessOptions {
/**
* Attempt to fix errors when decoding the input file rather than returning an Err.
* Default: `false`
*/
fixErrors?: boolean | undefined | null
/**
* Write to output even if there was no improvement in compression.
* Default: `false`
*/
force?: boolean | undefined | null
/** Which filters to try on the file (0-5) */
filter?: Array<number> | undefined | null
/**
* Whether to attempt bit depth reduction
* Default: `true`
*/
bitDepthReduction?: boolean | undefined | null
/**
* Whether to attempt color type reduction
* Default: `true`
*/
colorTypeReduction?: boolean | undefined | null
/**
* Whether to attempt palette reduction
* Default: `true`
*/
paletteReduction?: boolean | undefined | null
/**
* Whether to attempt grayscale reduction
* Default: `true`
*/
grayscaleReduction?: boolean | undefined | null
/**
* Whether to perform IDAT recoding
* If any type of reduction is performed, IDAT recoding will be performed regardless of this setting
* Default: `true`
*/
idatRecoding?: boolean | undefined | null
/** Whether to remove ***All non-critical headers*** on PNG */
strip?: boolean | undefined | null
/** Whether to use heuristics to pick the best filter and compression */
useHeuristics?: boolean | undefined | null
}
export function losslessCompressPng(input: Buffer, options?: PNGLosslessOptions | undefined | null): Buffer
```
### `JPEG`
```ts
export interface JpegCompressOptions {
/** Output quality, default is 100 (lossless) */
quality?: number | undefined | null
/**
* If true, it will use MozJPEGs scan optimization. Makes progressive image files smaller.
* Default is `true`
*/
optimizeScans?: boolean | undefined | null
}
export function compressJpeg(input: Buffer, options?: JpegCompressOptions | undefined | null): Buffer
```

View File

@ -0,0 +1,18 @@
import { promises as fs } from 'fs'
import test from 'ava'
import { losslessCompressPng, compressJpeg } from '../index.js'
const PNG = await fs.readFile('un-optimized.png')
const JPEG = await fs.readFile('un-optimized.jpg')
test('should be able to lossless optimize png image', async (t) => {
const dest = losslessCompressPng(PNG)
t.true(dest.length < PNG.length)
})
test('should be able to lossless optimize jpeg image', async (t) => {
const dest = compressJpeg(JPEG, { quality: 100 })
t.true(dest.length < PNG.length)
})

5
build.rs Normal file
View File

@ -0,0 +1,5 @@
extern crate napi_build;
fn main() {
napi_build::setup();
}

66
index.d.ts vendored Normal file
View File

@ -0,0 +1,66 @@
/* tslint:disable */
/* eslint-disable */
/* auto-generated by NAPI-RS */
export class ExternalObject<T> {
readonly '': {
readonly '': unique symbol
[K: symbol]: T
}
}
export interface PNGLosslessOptions {
/**
* Attempt to fix errors when decoding the input file rather than returning an Err.
* Default: `false`
*/
fixErrors?: boolean | undefined | null
/**
* Write to output even if there was no improvement in compression.
* Default: `false`
*/
force?: boolean | undefined | null
/** Which filters to try on the file (0-5) */
filter?: Array<number> | undefined | null
/**
* Whether to attempt bit depth reduction
* Default: `true`
*/
bitDepthReduction?: boolean | undefined | null
/**
* Whether to attempt color type reduction
* Default: `true`
*/
colorTypeReduction?: boolean | undefined | null
/**
* Whether to attempt palette reduction
* Default: `true`
*/
paletteReduction?: boolean | undefined | null
/**
* Whether to attempt grayscale reduction
* Default: `true`
*/
grayscaleReduction?: boolean | undefined | null
/**
* Whether to perform IDAT recoding
* If any type of reduction is performed, IDAT recoding will be performed regardless of this setting
* Default: `true`
*/
idatRecoding?: boolean | undefined | null
/** Whether to remove ***All non-critical headers*** on PNG */
strip?: boolean | undefined | null
/** Whether to use heuristics to pick the best filter and compression */
useHeuristics?: boolean | undefined | null
}
export function losslessCompressPng(input: Buffer, options?: PNGLosslessOptions | undefined | null): Buffer
export interface JpegCompressOptions {
/** Output quality, default is 100 (lossless) */
quality?: number | undefined | null
/**
* If true, it will use MozJPEGs scan optimization. Makes progressive image files smaller.
* Default is `true`
*/
optimizeScans?: boolean | undefined | null
}
export function compressJpeg(input: Buffer, options?: JpegCompressOptions | undefined | null): Buffer

209
index.js Normal file
View File

@ -0,0 +1,209 @@
const { existsSync, readFileSync } = require('fs')
const { join } = require('path')
const { platform, arch } = process
let nativeBinding = null
let localFileExisted = false
let loadError = null
function isMusl() {
// For Node 10
if (!process.report || typeof process.report.getReport !== 'function') {
try {
return readFileSync('/usr/bin/ldd', 'utf8').includes('musl')
} catch (e) {
return false
}
} else {
const { glibcVersionRuntime } = process.report.getReport().header
return !Boolean(glibcVersionRuntime)
}
}
switch (platform) {
case 'android':
if (arch !== 'arm64') {
throw new Error(`Unsupported architecture on Android ${arch}`)
}
localFileExisted = existsSync(join(__dirname, 'image.android-arm64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./image.android-arm64.node')
} else {
nativeBinding = require('@napi-rs/image-android-arm64')
}
} catch (e) {
loadError = e
}
break
case 'win32':
switch (arch) {
case 'x64':
localFileExisted = existsSync(join(__dirname, 'image.win32-x64-msvc.node'))
try {
if (localFileExisted) {
nativeBinding = require('./image.win32-x64-msvc.node')
} else {
nativeBinding = require('@napi-rs/image-win32-x64-msvc')
}
} catch (e) {
loadError = e
}
break
case 'ia32':
localFileExisted = existsSync(join(__dirname, 'image.win32-ia32-msvc.node'))
try {
if (localFileExisted) {
nativeBinding = require('./image.win32-ia32-msvc.node')
} else {
nativeBinding = require('@napi-rs/image-win32-ia32-msvc')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(join(__dirname, 'image.win32-arm64-msvc.node'))
try {
if (localFileExisted) {
nativeBinding = require('./image.win32-arm64-msvc.node')
} else {
nativeBinding = require('@napi-rs/image-win32-arm64-msvc')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Windows: ${arch}`)
}
break
case 'darwin':
switch (arch) {
case 'x64':
localFileExisted = existsSync(join(__dirname, 'image.darwin-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./image.darwin-x64.node')
} else {
nativeBinding = require('@napi-rs/image-darwin-x64')
}
} catch (e) {
loadError = e
}
break
case 'arm64':
localFileExisted = existsSync(join(__dirname, 'image.darwin-arm64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./image.darwin-arm64.node')
} else {
nativeBinding = require('@napi-rs/image-darwin-arm64')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on macOS: ${arch}`)
}
break
case 'freebsd':
if (arch !== 'x64') {
throw new Error(`Unsupported architecture on FreeBSD: ${arch}`)
}
localFileExisted = existsSync(join(__dirname, 'image.freebsd-x64.node'))
try {
if (localFileExisted) {
nativeBinding = require('./image.freebsd-x64.node')
} else {
nativeBinding = require('@napi-rs/image-freebsd-x64')
}
} catch (e) {
loadError = e
}
break
case 'linux':
switch (arch) {
case 'x64':
if (isMusl()) {
localFileExisted = existsSync(join(__dirname, 'image.linux-x64-musl.node'))
try {
if (localFileExisted) {
nativeBinding = require('./image.linux-x64-musl.node')
} else {
nativeBinding = require('@napi-rs/image-linux-x64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(join(__dirname, 'image.linux-x64-gnu.node'))
try {
if (localFileExisted) {
nativeBinding = require('./image.linux-x64-gnu.node')
} else {
nativeBinding = require('@napi-rs/image-linux-x64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm64':
if (isMusl()) {
localFileExisted = existsSync(join(__dirname, 'image.linux-arm64-musl.node'))
try {
if (localFileExisted) {
nativeBinding = require('./image.linux-arm64-musl.node')
} else {
nativeBinding = require('@napi-rs/image-linux-arm64-musl')
}
} catch (e) {
loadError = e
}
} else {
localFileExisted = existsSync(join(__dirname, 'image.linux-arm64-gnu.node'))
try {
if (localFileExisted) {
nativeBinding = require('./image.linux-arm64-gnu.node')
} else {
nativeBinding = require('@napi-rs/image-linux-arm64-gnu')
}
} catch (e) {
loadError = e
}
}
break
case 'arm':
localFileExisted = existsSync(join(__dirname, 'image.linux-arm-gnueabihf.node'))
try {
if (localFileExisted) {
nativeBinding = require('./image.linux-arm-gnueabihf.node')
} else {
nativeBinding = require('@napi-rs/image-linux-arm-gnueabihf')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Linux: ${arch}`)
}
break
default:
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
}
if (!nativeBinding) {
if (loadError) {
throw loadError
}
throw new Error(`Failed to load native binding`)
}
const { losslessCompressPng, compressJpeg } = nativeBinding
module.exports.losslessCompressPng = losslessCompressPng
module.exports.compressJpeg = compressJpeg

View File

@ -0,0 +1,3 @@
# `@napi-rs/image-android-arm-eabi`
This is the **armv7-linux-androideabi** binary for `@napi-rs/image`

View File

@ -0,0 +1,37 @@
{
"name": "@napi-rs/image-android-arm-eabi",
"version": "0.0.0",
"os": [
"android"
],
"cpu": [
"arm"
],
"main": "image.android-arm-eabi.node",
"files": [
"image.android-arm-eabi.node"
],
"description": "Image processing library",
"keywords": [
"image",
"lossless",
"compression",
"jpeg",
"jpg",
"png"
],
"author": {
"email": "github@lyn.one",
"name": "LongYinan",
"url": "https://lyn.one"
},
"license": "MIT",
"engines": {
"node": ">= 10"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"repository": "git@github.com:Brooooooklyn/imgquant.git"
}

View File

@ -0,0 +1,3 @@
# `@napi-rs/image-android-arm64`
This is the **aarch64-linux-android** binary for `@napi-rs/image`

View File

@ -0,0 +1,37 @@
{
"name": "@napi-rs/image-android-arm64",
"version": "0.0.0",
"os": [
"android"
],
"cpu": [
"arm64"
],
"main": "image.android-arm64.node",
"files": [
"image.android-arm64.node"
],
"description": "Image processing library",
"keywords": [
"image",
"lossless",
"compression",
"jpeg",
"jpg",
"png"
],
"author": {
"email": "github@lyn.one",
"name": "LongYinan",
"url": "https://lyn.one"
},
"license": "MIT",
"engines": {
"node": ">= 10"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"repository": "git@github.com:Brooooooklyn/imgquant.git"
}

View File

@ -0,0 +1,3 @@
# `@napi-rs/image-darwin-arm64`
This is the **aarch64-apple-darwin** binary for `@napi-rs/image`

View File

@ -0,0 +1,37 @@
{
"name": "@napi-rs/image-darwin-arm64",
"version": "0.0.0",
"os": [
"darwin"
],
"cpu": [
"arm64"
],
"main": "image.darwin-arm64.node",
"files": [
"image.darwin-arm64.node"
],
"description": "Image processing library",
"keywords": [
"image",
"lossless",
"compression",
"jpeg",
"jpg",
"png"
],
"author": {
"email": "github@lyn.one",
"name": "LongYinan",
"url": "https://lyn.one"
},
"license": "MIT",
"engines": {
"node": ">= 10"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"repository": "git@github.com:Brooooooklyn/imgquant.git"
}

3
npm/darwin-x64/README.md Normal file
View File

@ -0,0 +1,3 @@
# `@napi-rs/image-darwin-x64`
This is the **x86_64-apple-darwin** binary for `@napi-rs/image`

View File

@ -0,0 +1,37 @@
{
"name": "@napi-rs/image-darwin-x64",
"version": "0.0.0",
"os": [
"darwin"
],
"cpu": [
"x64"
],
"main": "image.darwin-x64.node",
"files": [
"image.darwin-x64.node"
],
"description": "Image processing library",
"keywords": [
"image",
"lossless",
"compression",
"jpeg",
"jpg",
"png"
],
"author": {
"email": "github@lyn.one",
"name": "LongYinan",
"url": "https://lyn.one"
},
"license": "MIT",
"engines": {
"node": ">= 10"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"repository": "git@github.com:Brooooooklyn/imgquant.git"
}

View File

@ -0,0 +1,3 @@
# `@napi-rs/image-freebsd-x64`
This is the **x86_64-unknown-freebsd** binary for `@napi-rs/image`

View File

@ -0,0 +1,37 @@
{
"name": "@napi-rs/image-freebsd-x64",
"version": "0.0.0",
"os": [
"freebsd"
],
"cpu": [
"x64"
],
"main": "image.freebsd-x64.node",
"files": [
"image.freebsd-x64.node"
],
"description": "Image processing library",
"keywords": [
"image",
"lossless",
"compression",
"jpeg",
"jpg",
"png"
],
"author": {
"email": "github@lyn.one",
"name": "LongYinan",
"url": "https://lyn.one"
},
"license": "MIT",
"engines": {
"node": ">= 10"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"repository": "git@github.com:Brooooooklyn/imgquant.git"
}

View File

@ -0,0 +1,3 @@
# `@napi-rs/image-linux-arm-gnueabihf`
This is the **armv7-unknown-linux-gnueabihf** binary for `@napi-rs/image`

View File

@ -0,0 +1,37 @@
{
"name": "@napi-rs/image-linux-arm-gnueabihf",
"version": "0.0.0",
"os": [
"linux"
],
"cpu": [
"arm"
],
"main": "image.linux-arm-gnueabihf.node",
"files": [
"image.linux-arm-gnueabihf.node"
],
"description": "Image processing library",
"keywords": [
"image",
"lossless",
"compression",
"jpeg",
"jpg",
"png"
],
"author": {
"email": "github@lyn.one",
"name": "LongYinan",
"url": "https://lyn.one"
},
"license": "MIT",
"engines": {
"node": ">= 10"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"repository": "git@github.com:Brooooooklyn/imgquant.git"
}

View File

@ -0,0 +1,3 @@
# `@napi-rs/image-linux-arm64-gnu`
This is the **aarch64-unknown-linux-gnu** binary for `@napi-rs/image`

View File

@ -0,0 +1,13 @@
// Node.js 10.x, ignore
if (!process.report || typeof process.report.getReport !== 'function') {
process.exit(0)
}
// Only GNU system has this field
const { glibcVersionRuntime } = process.report.getReport().header
if (glibcVersionRuntime) {
process.exit(0)
} else {
process.exit(1)
}

View File

@ -0,0 +1,41 @@
{
"name": "@napi-rs/image-linux-arm64-gnu",
"version": "0.0.0",
"os": [
"linux"
],
"cpu": [
"arm64"
],
"main": "image.linux-arm64-gnu.node",
"files": [
"image.linux-arm64-gnu.node",
"install.js"
],
"description": "Image processing library",
"keywords": [
"image",
"lossless",
"compression",
"jpeg",
"jpg",
"png"
],
"author": {
"email": "github@lyn.one",
"name": "LongYinan",
"url": "https://lyn.one"
},
"license": "MIT",
"engines": {
"node": ">= 10"
},
"scripts": {
"install": "node install.js"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"repository": "git@github.com:Brooooooklyn/imgquant.git"
}

View File

@ -0,0 +1,3 @@
# `@napi-rs/image-linux-arm64-musl`
This is the **aarch64-unknown-linux-musl** binary for `@napi-rs/image`

View File

@ -0,0 +1,13 @@
// Node.js 10.x, ignore
if (!process.report || typeof process.report.getReport !== 'function') {
process.exit(0)
}
// Only GNU system has this field
const { glibcVersionRuntime } = process.report.getReport().header
if (glibcVersionRuntime) {
process.exit(1)
} else {
process.exit(0)
}

View File

@ -0,0 +1,41 @@
{
"name": "@napi-rs/image-linux-arm64-musl",
"version": "0.0.0",
"os": [
"linux"
],
"cpu": [
"arm64"
],
"main": "image.linux-arm64-musl.node",
"files": [
"image.linux-arm64-musl.node",
"install.js"
],
"description": "Image processing library",
"keywords": [
"image",
"lossless",
"compression",
"jpeg",
"jpg",
"png"
],
"author": {
"email": "github@lyn.one",
"name": "LongYinan",
"url": "https://lyn.one"
},
"license": "MIT",
"engines": {
"node": ">= 10"
},
"scripts": {
"install": "node install.js"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"repository": "git@github.com:Brooooooklyn/imgquant.git"
}

View File

@ -0,0 +1,3 @@
# `@napi-rs/image-linux-x64-gnu`
This is the **x86_64-unknown-linux-gnu** binary for `@napi-rs/image`

View File

@ -0,0 +1,13 @@
// Node.js 10.x, ignore
if (!process.report || typeof process.report.getReport !== 'function') {
process.exit(0)
}
// Only GNU system has this field
const { glibcVersionRuntime } = process.report.getReport().header
if (glibcVersionRuntime) {
process.exit(0)
} else {
process.exit(1)
}

View File

@ -0,0 +1,41 @@
{
"name": "@napi-rs/image-linux-x64-gnu",
"version": "0.0.0",
"os": [
"linux"
],
"cpu": [
"x64"
],
"main": "image.linux-x64-gnu.node",
"files": [
"image.linux-x64-gnu.node",
"install.js"
],
"description": "Image processing library",
"keywords": [
"image",
"lossless",
"compression",
"jpeg",
"jpg",
"png"
],
"author": {
"email": "github@lyn.one",
"name": "LongYinan",
"url": "https://lyn.one"
},
"license": "MIT",
"engines": {
"node": ">= 10"
},
"scripts": {
"install": "node install.js"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"repository": "git@github.com:Brooooooklyn/imgquant.git"
}

View File

@ -0,0 +1,3 @@
# `@napi-rs/image-linux-x64-musl`
This is the **x86_64-unknown-linux-musl** binary for `@napi-rs/image`

View File

@ -0,0 +1,13 @@
// Node.js 10.x, ignore
if (!process.report || typeof process.report.getReport !== 'function') {
process.exit(0)
}
// Only GNU system has this field
const { glibcVersionRuntime } = process.report.getReport().header
if (glibcVersionRuntime) {
process.exit(1)
} else {
process.exit(0)
}

View File

@ -0,0 +1,41 @@
{
"name": "@napi-rs/image-linux-x64-musl",
"version": "0.0.0",
"os": [
"linux"
],
"cpu": [
"x64"
],
"main": "image.linux-x64-musl.node",
"files": [
"image.linux-x64-musl.node",
"install.js"
],
"description": "Image processing library",
"keywords": [
"image",
"lossless",
"compression",
"jpeg",
"jpg",
"png"
],
"author": {
"email": "github@lyn.one",
"name": "LongYinan",
"url": "https://lyn.one"
},
"license": "MIT",
"engines": {
"node": ">= 10"
},
"scripts": {
"install": "node install.js"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"repository": "git@github.com:Brooooooklyn/imgquant.git"
}

View File

@ -0,0 +1,3 @@
# `@napi-rs/image-win32-ia32-msvc`
This is the **i686-pc-windows-msvc** binary for `@napi-rs/image`

View File

@ -0,0 +1,37 @@
{
"name": "@napi-rs/image-win32-ia32-msvc",
"version": "0.0.0",
"os": [
"win32"
],
"cpu": [
"ia32"
],
"main": "image.win32-ia32-msvc.node",
"files": [
"image.win32-ia32-msvc.node"
],
"description": "Image processing library",
"keywords": [
"image",
"lossless",
"compression",
"jpeg",
"jpg",
"png"
],
"author": {
"email": "github@lyn.one",
"name": "LongYinan",
"url": "https://lyn.one"
},
"license": "MIT",
"engines": {
"node": ">= 10"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"repository": "git@github.com:Brooooooklyn/imgquant.git"
}

View File

@ -0,0 +1,3 @@
# `@napi-rs/image-win32-x64-msvc`
This is the **x86_64-pc-windows-msvc** binary for `@napi-rs/image`

View File

@ -0,0 +1,37 @@
{
"name": "@napi-rs/image-win32-x64-msvc",
"version": "0.0.0",
"os": [
"win32"
],
"cpu": [
"x64"
],
"main": "image.win32-x64-msvc.node",
"files": [
"image.win32-x64-msvc.node"
],
"description": "Image processing library",
"keywords": [
"image",
"lossless",
"compression",
"jpeg",
"jpg",
"png"
],
"author": {
"email": "github@lyn.one",
"name": "LongYinan",
"url": "https://lyn.one"
},
"license": "MIT",
"engines": {
"node": ">= 10"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"repository": "git@github.com:Brooooooklyn/imgquant.git"
}

7
optimize-test.js Normal file
View File

@ -0,0 +1,7 @@
const { readFileSync, writeFileSync } = require('fs')
const { losslessCompressPng, compressJpeg } = require('./index')
writeFileSync('optimized-lossless.png', losslessCompressPng(readFileSync('./un-optimized.png')))
writeFileSync('optimized-lossless.jpg', compressJpeg(readFileSync('./un-optimized.jpg')))

83
package.json Normal file
View File

@ -0,0 +1,83 @@
{
"name": "@napi-rs/image",
"version": "0.0.0",
"main": "index.js",
"types": "index.d.ts",
"description": "Image processing library",
"author": {
"email": "github@lyn.one",
"name": "LongYinan",
"url": "https://lyn.one"
},
"keywords": [
"image",
"lossless",
"compression",
"jpeg",
"jpg",
"png"
],
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
},
"napi": {
"name": "image",
"triples": {
"additional": [
"aarch64-apple-darwin",
"aarch64-linux-android",
"aarch64-unknown-linux-gnu",
"aarch64-unknown-linux-musl",
"armv7-unknown-linux-gnueabihf",
"x86_64-unknown-linux-musl",
"x86_64-unknown-freebsd",
"i686-pc-windows-msvc",
"armv7-linux-androideabi"
]
}
},
"license": "MIT",
"devDependencies": {
"@napi-rs/cli": "^2.2.1",
"@types/node": "^17.0.8",
"ava": "^4.0.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.5.1"
},
"ava": {
"extensions": [
"mjs"
],
"timeout": "3m",
"environmentVariables": {
"NODE_ENV": "ava"
}
},
"engines": {
"node": ">= 10"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
},
"scripts": {
"artifacts": "napi artifacts",
"build": "napi build --platform --release",
"build:debug": "napi build --platform",
"format": "run-p format:prettier format:rs",
"format:prettier": "prettier --config ./package.json -w .",
"format:rs": "cargo fmt --all",
"prepublishOnly": "napi prepublish -t npm",
"test": "ava",
"version": "napi version"
},
"prettier": {
"printWidth": 120,
"semi": false,
"trailingComma": "all",
"singleQuote": true,
"arrowParens": "always"
},
"repository": "git@github.com:Brooooooklyn/imgquant.git"
}

2
rustfmt.toml Normal file
View File

@ -0,0 +1,2 @@
tab_spaces = 2
edition = "2021"

185
src/lib.rs Normal file
View File

@ -0,0 +1,185 @@
#![deny(clippy::all)]
use std::iter::FromIterator;
use napi::{bindgen_prelude::*, JsBuffer};
use napi_derive::napi;
#[napi(object, js_name = "PNGLosslessOptions")]
#[derive(Default)]
pub struct PNGLosslessOptions {
/// Attempt to fix errors when decoding the input file rather than returning an Err.
/// Default: `false`
pub fix_errors: Option<bool>,
/// Write to output even if there was no improvement in compression.
/// Default: `false`
pub force: Option<bool>,
/// Which filters to try on the file (0-5)
pub filter: Option<Vec<u32>>,
/// Whether to attempt bit depth reduction
/// Default: `true`
pub bit_depth_reduction: Option<bool>,
/// Whether to attempt color type reduction
/// Default: `true`
pub color_type_reduction: Option<bool>,
/// Whether to attempt palette reduction
/// Default: `true`
pub palette_reduction: Option<bool>,
/// Whether to attempt grayscale reduction
/// Default: `true`
pub grayscale_reduction: Option<bool>,
/// Whether to perform IDAT recoding
/// If any type of reduction is performed, IDAT recoding will be performed regardless of this setting
/// Default: `true`
pub idat_recoding: Option<bool>,
/// Whether to remove ***All non-critical headers*** on PNG
pub strip: Option<bool>,
/// Whether to use heuristics to pick the best filter and compression
pub use_heuristics: Option<bool>,
}
#[inline(always)]
fn to_oxipng_options(options: Option<PNGLosslessOptions>) -> oxipng::Options {
let opt = options.unwrap_or_default();
oxipng::Options {
fix_errors: opt.fix_errors.unwrap_or(false),
force: opt.force.unwrap_or(false),
filter: opt
.filter
.map(|v| v.into_iter().map(|i| i as u8).collect())
.unwrap_or_else(|| oxipng::IndexSet::from_iter(0..5)),
bit_depth_reduction: opt.bit_depth_reduction.unwrap_or(true),
color_type_reduction: opt.color_type_reduction.unwrap_or(true),
palette_reduction: opt.palette_reduction.unwrap_or(true),
grayscale_reduction: opt.grayscale_reduction.unwrap_or(true),
idat_recoding: opt.idat_recoding.unwrap_or(true),
strip: opt
.strip
.map(|s| {
if s {
oxipng::Headers::All
} else {
oxipng::Headers::None
}
})
.unwrap_or(oxipng::Headers::All),
use_heuristics: opt.use_heuristics.unwrap_or(true),
#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))]
deflate: oxipng::Deflaters::Libdeflater,
..Default::default()
}
}
#[napi]
pub fn lossless_compress_png(input: Buffer, options: Option<PNGLosslessOptions>) -> Result<Buffer> {
let output = oxipng::optimize_from_memory(input.as_ref(), &to_oxipng_options(options))
.map_err(|err| Error::new(Status::InvalidArg, format!("Optimize failed {}", err)))?;
Ok(output.into())
}
#[napi(object)]
#[derive(Default)]
pub struct JpegCompressOptions {
/// Output quality, default is 100 (lossless)
pub quality: Option<u32>,
/// If true, it will use MozJPEGs scan optimization. Makes progressive image files smaller.
/// Default is `true`
pub optimize_scans: Option<bool>,
}
#[napi]
pub unsafe fn compress_jpeg(
env: Env,
input: Buffer,
options: Option<JpegCompressOptions>,
) -> Result<JsBuffer> {
std::panic::catch_unwind(|| {
let opts = options.unwrap_or_default();
let mut de_c_info: mozjpeg_sys::jpeg_decompress_struct = std::mem::zeroed();
let mut err_handler = create_error_handler();
de_c_info.common.err = &mut err_handler;
mozjpeg_sys::jpeg_create_decompress(&mut de_c_info);
let input_buf = input.as_ref();
#[cfg(any(target_os = "windows", target_arch = "arm"))]
mozjpeg_sys::jpeg_mem_src(&mut de_c_info, input_buf.as_ptr(), input_buf.len() as u32);
#[cfg(not(any(target_os = "windows", target_arch = "arm")))]
mozjpeg_sys::jpeg_mem_src(&mut de_c_info, input_buf.as_ptr(), input_buf.len() as u64);
let mut compress_c_info: mozjpeg_sys::jpeg_compress_struct = std::mem::zeroed();
compress_c_info.optimize_coding = 1;
compress_c_info.common.err = &mut err_handler;
mozjpeg_sys::jpeg_create_compress(&mut compress_c_info);
mozjpeg_sys::jpeg_read_header(&mut de_c_info, 1);
let src_coef_arrays = mozjpeg_sys::jpeg_read_coefficients(&mut de_c_info);
mozjpeg_sys::jpeg_copy_critical_parameters(&de_c_info, &mut compress_c_info);
if let Some(quality) = opts.quality {
mozjpeg_sys::jpeg_set_quality(&mut compress_c_info, quality as i32, 0);
}
if opts.optimize_scans.unwrap_or(true) {
mozjpeg_sys::jpeg_c_set_bool_param(
&mut compress_c_info,
mozjpeg_sys::J_BOOLEAN_PARAM::JBOOLEAN_OPTIMIZE_SCANS,
1,
);
}
mozjpeg_sys::jpeg_c_set_int_param(
&mut compress_c_info,
mozjpeg_sys::J_INT_PARAM::JINT_DC_SCAN_OPT_MODE,
0,
);
let mut buf = std::ptr::null_mut();
let mut outsize = 0;
mozjpeg_sys::jpeg_mem_dest(&mut compress_c_info, &mut buf, &mut outsize);
mozjpeg_sys::jpeg_write_coefficients(&mut compress_c_info, src_coef_arrays);
mozjpeg_sys::jpeg_finish_compress(&mut compress_c_info);
mozjpeg_sys::jpeg_finish_decompress(&mut de_c_info);
env
.create_buffer_with_borrowed_data(
buf,
outsize as usize,
(de_c_info, compress_c_info, buf),
|(mut input, mut output, buf), _| {
mozjpeg_sys::jpeg_destroy_decompress(&mut input);
mozjpeg_sys::jpeg_destroy_compress(&mut output);
libc::free(buf as *mut std::ffi::c_void);
},
)
.map(|v| v.into_raw())
})
.map_err(|err| {
Error::new(
Status::GenericFailure,
format!("Compress JPEG failed {:?}", err),
)
})
.and_then(|v| v)
}
unsafe fn create_error_handler() -> mozjpeg_sys::jpeg_error_mgr {
let mut err: mozjpeg_sys::jpeg_error_mgr = std::mem::zeroed();
mozjpeg_sys::jpeg_std_error(&mut err);
err.error_exit = Some(unwind_error_exit);
err.emit_message = Some(silence_message);
err
}
extern "C" fn unwind_error_exit(cinfo: &mut mozjpeg_sys::jpeg_common_struct) {
let message = unsafe {
let err = cinfo.err.as_ref().unwrap();
match err.format_message {
Some(fmt) => {
let buffer = std::mem::zeroed();
fmt(cinfo, &buffer);
let len = buffer.iter().take_while(|&&c| c != 0).count();
String::from_utf8_lossy(&buffer[..len]).into()
}
None => format!("libjpeg error: {}", err.msg_code),
}
};
std::panic::resume_unwind(Box::new(message))
}
extern "C" fn silence_message(
_cinfo: &mut mozjpeg_sys::jpeg_common_struct,
_level: std::os::raw::c_int,
) {
}

BIN
un-optimized.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

BIN
un-optimized.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 KiB

1605
yarn.lock Normal file

File diff suppressed because it is too large Load Diff