Merge pull request #6 from Brooooooklyn/webp

feat: support webp
This commit is contained in:
LongYinan 2022-03-28 23:34:55 +08:00 committed by GitHub
commit 5511848d5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1267 additions and 1242 deletions

View File

@ -25,7 +25,6 @@ jobs:
settings:
- host: macos-latest
target: x86_64-apple-darwin
architecture: x64
build: |
yarn build -- --features with_simd
strip -x packages/*/*.node
@ -33,29 +32,28 @@ jobs:
build: |
yarn build -- --features with_simd
target: x86_64-pc-windows-msvc
architecture: x64
- host: windows-latest
build: |
yarn build -- --features with_simd
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
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
build: |
docker run --env RUST_TARGET --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd):/build -w /build builder yarn build -- --features with_simd && strip packages/*/*.node
set -e && \
yarn build -- --features with_simd --target x86_64-unknown-linux-gnu && \
strip packages/*/*.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 --env RUST_TARGET --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 -- --features with_simd && strip packages/*/*.node"
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
build: >-
set -e &&
unset RUSTFLAGS &&
unset CC &&
yarn build -- --features with_simd &&
strip packages/*/*.node
- host: macos-latest
target: aarch64-apple-darwin
build: |
@ -103,24 +101,23 @@ jobs:
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 --env RUST_TARGET --rm -v ~/.cargo/git:/root/.cargo/git -v ~/.cargo/registry:/root/.cargo/registry -v $(pwd):/build -w /build builder sh -c "yarn build -- --features with_simd && /aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip packages/*/*.node"
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
build: >-
set -e &&
yarn build -- --target aarch64-unknown-linux-musl --features with_simd &&
/aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip packages/*/*.node
name: stable - ${{ matrix.settings.target }} - node@16
runs-on: ${{ matrix.settings.host }}
env:
RUST_TARGET: ${{ matrix.settings.target }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v2
uses: actions/setup-node@v3
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'
@ -131,38 +128,50 @@ jobs:
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
uses: actions/cache@v3
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') }}
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
.cargo-cache/registry/index/
.cargo-cache/registry/cache/
.cargo-cache/git/db/
target/
key: ${{ matrix.settings.target }}-cargo-registry
- name: Cache NPM dependencies
uses: actions/cache@v2
uses: actions/cache@v3
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: Setup node x86 install settings
if: matrix.settings.target == 'i686-pc-windows-msvc'
run: yarn config set supportedArchitectures.cpu --json '["x86", "x64"]'
shell: bash
- name: Install dependencies
run: yarn install
run: yarn install --immutable --mode=skip-build
- name: Setup node x86
uses: actions/setup-node@v3
if: matrix.settings.target == 'i686-pc-windows-msvc'
with:
node-version: 16
check-latest: true
architecture: x86
- name: Build in docker
uses: addnab/docker-run-action@v3
if: ${{ matrix.settings.docker }}
with:
image: ${{ matrix.settings.docker }}
options: --user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/root/.cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/root/.cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/root/.cargo/registry/index -v ${{ github.workspace }}:/build -w /build
run: ${{ matrix.settings.build }}
- name: Build
run: ${{ matrix.settings.build }}
if: ${{ !matrix.settings.docker }}
shell: bash
- name: Upload artifact
uses: actions/upload-artifact@v2
@ -174,10 +183,10 @@ jobs:
runs-on: macos-10.15
name: Build FreeBSD
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Build
id: build
uses: vmactions/freebsd-vm@v0.1.5
uses: vmactions/freebsd-vm@v0.1.6
env:
DEBUG: napi:*
RUSTUP_HOME: /usr/local/rustup
@ -208,7 +217,7 @@ jobs:
whoami
env
freebsd-version
yarn install
yarn install --immutable --mode=skip-build
yarn build -- --features with_simd
strip -x packages/*/*.node
yarn test
@ -236,20 +245,20 @@ jobs:
- '17'
runs-on: ${{ matrix.settings.host }}
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
check-latest: true
cache: yarn
- name: Cache NPM dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: node_modules
key: npm-cache-test-${{ matrix.settings.target }}-${{ matrix.node }}-${{ hashFiles('yarn.lock') }}
- name: Install dependencies
run: yarn install
run: yarn install --immutable --mode=skip-build
- name: Download artifacts
uses: actions/download-artifact@v2
with:
@ -276,20 +285,20 @@ jobs:
- '17'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
check-latest: true
cache: yarn
- name: Cache NPM dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: node_modules
key: npm-cache-test-linux-x64-gnu-${{ matrix.node }}-${{ hashFiles('yarn.lock') }}
- name: Install dependencies
run: yarn install
run: yarn install --immutable --mode=skip-build
- name: Download artifacts
uses: actions/download-artifact@v2
with:
@ -316,20 +325,20 @@ jobs:
- '17'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
check-latest: true
cache: yarn
- name: Cache NPM dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: node_modules
key: npm-cache-test-x86_64-unknown-linux-musl-${{ matrix.node }}-${{ hashFiles('yarn.lock') }}
- name: Install dependencies
run: yarn install
run: yarn install --immutable --mode=skip-build
- name: Download artifacts
uses: actions/download-artifact@v2
with:
@ -357,19 +366,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- run: docker run --rm --privileged multiarch/qemu-user-static:register --reset
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Download artifacts
uses: actions/download-artifact@v2
with:
name: bindings-aarch64-unknown-linux-gnu
path: artifacts
- name: Cache NPM dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: node_modules
key: npm-cache-test-linux-aarch64-gnu-${{ matrix.node }}-${{ hashFiles('yarn.lock') }}
- name: Install dependencies
run: yarn install
run: yarn install --immutable --mode=skip-build
- name: Move artifacts
run: yarn artifacts
shell: bash
@ -399,19 +408,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- run: docker run --rm --privileged multiarch/qemu-user-static:register --reset
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Download artifacts
uses: actions/download-artifact@v2
with:
name: bindings-armv7-unknown-linux-gnueabihf
path: artifacts
- name: Cache NPM dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: node_modules
key: npm-cache-test-linux-arm-gnueabihf-${{ matrix.node }}-${{ hashFiles('yarn.lock') }}
- name: Install dependencies
run: yarn install
run: yarn install --immutable --mode=skip-build
- name: Move artifacts
run: yarn artifacts
shell: bash
@ -438,22 +447,22 @@ jobs:
- test-linux-aarch64-gnu-binding
- test-linux-arm-gnueabihf-binding
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup node
uses: actions/setup-node@v2
uses: actions/setup-node@v3
with:
node-version: 16
check-latest: true
cache: yarn
- name: Cache NPM dependencies
uses: actions/cache@v2
uses: actions/cache@v3
with:
path: node_modules
key: npm-cache-ubuntu-latest-${{ hashFiles('yarn.lock') }}
restore-keys: |
npm-cache-
- name: Install dependencies
run: yarn install
run: yarn install --immutable --mode=skip-build
- name: Download all artifacts
uses: actions/download-artifact@v2
with:

File diff suppressed because one or more lines are too long

785
.yarn/releases/yarn-3.2.0.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.1.1.cjs
yarnPath: .yarn/releases/yarn-3.2.0.cjs

View File

@ -1,61 +1,8 @@
const { readFileSync, writeFileSync } = require('fs')
const { losslessCompressPng, compressJpeg, pngQuantize, svgMin } = require('./packages/binding')
const { losslessCompressPng, compressJpeg, pngQuantize, losslessWebpFromPng } = require('./packages/binding')
const PNG = readFileSync('./un-optimized.png')
const SVG = `<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="489.589px" height="489.589px" viewBox="0 0 489.589 489.589" style="enable-background:new 0 0 489.589 489.589;"
xml:space="preserve">
<g>
<g>
<path d="M484.536,30.412c-1.034,3.078-2.545,6.233-4.652,9.279c-7.39,10.654-18.507,15.733-24.838,11.343
c-3.896-2.707-5.046-8.383-3.635-14.827c-6.448,1.404-12.127,0.258-14.836-3.645c-4.384-6.324,0.701-17.44,11.35-24.832
c3.041-2.108,6.199-3.625,9.28-4.652l-1.698-1.687L427.246,0l-10.531,10.545c0.204,12.864-5.843,27.475-17.549,39.17
c-11.698,11.7-26.305,17.751-39.177,17.544l-0.705,0.709c7.942,9.993,5.113,26.908-6.645,39.649l-97.701,99.254
c-16.212-1.154-40.134-7.153-48.211-15.222c-15.333-15.333-3.178-38.175,25.773-48.339c12.693-4.462,31.346-12.728,48.71-17.909
l-95.248-0.21l-25.812,109.717l-79.421-17.376L0.782,363.311l90.57-47.548l-1.03,1.026c3.675,1.671,7.206,3.45,10.614,5.289
c-7.065,8.54-6.696,21.16,1.305,29.154l36.904,36.9c8.421,8.432,22.019,8.464,30.529,0.185c3.274,5.55,4.829,8.984,4.829,8.984
l2.136-2.132l-49.56,94.419l145.77-79.928l-17.895-81.815l108.21-25.455l-0.204-94.904c-5.186,17.288-13.397,35.814-17.833,48.445
c-10.167,28.95-33.014,41.093-48.338,25.776c-7.979-7.987-13.914-31.387-15.156-47.634l99.048-100.611
c12.819-11.066,29.378-13.393,38.992-5.171c-0.673-13.188,5.422-28.427,17.532-40.539c12.712-12.711,28.85-18.755,42.467-17.334
l9.137-9.137L487.43,33.31L484.536,30.412z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>
`
writeFileSync('optimized-lossless.png', losslessCompressPng(PNG))
@ -63,4 +10,4 @@ writeFileSync('quantized.png', pngQuantize(PNG))
writeFileSync('optimized-lossless.jpg', compressJpeg(readFileSync('./un-optimized.jpg')))
writeFileSync('optimized.svg', svgMin(SVG))
writeFileSync('optimized-lossless.webp', losslessWebpFromPng(PNG))

View File

@ -11,13 +11,13 @@
"packages/*"
],
"devDependencies": {
"@napi-rs/cli": "^2.4.0",
"@types/node": "^17.0.8",
"ava": "^4.0.1",
"@napi-rs/cli": "^2.5.0",
"@types/node": "^17.0.23",
"ava": "^4.1.0",
"lerna": "^4.0.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.5.1",
"typescript": "^4.5.4"
"prettier": "^2.6.1",
"typescript": "^4.6.3"
},
"scripts": {
"artifacts": "lerna run artifacts",
@ -41,6 +41,6 @@
"NODE_ENV": "ava"
}
},
"packageManager": "yarn@3.1.1",
"packageManager": "yarn@3.2.0",
"repository": "git@github.com:Brooooooklyn/Image.git"
}

View File

@ -12,13 +12,12 @@ oxipng_libdeflater = ["oxipng/libdeflater", "oxipng/parallel"]
with_simd = ["mozjpeg-sys/nasm_simd_parallel_build"]
[dependencies]
imagequant = "4.0.0-beta.8"
imagequant = "4.0.0"
libc = "0.2"
libwebp-sys = {version = "0.5", features = ["avx2", "sse41", "neon"]}
lodepng = "3"
napi = {version = "2", default-features = false, features = ["napi3"]}
napi-derive = {version = "2", default-features = false, features = ["type-def"]}
usvg = {version = "0.20", features = ["export"]}
xmlwriter = "0.1"
[dependencies.oxipng]
default-features = false

View File

@ -3,12 +3,6 @@
/* 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.
@ -71,16 +65,4 @@ export interface PngQuantOptions {
posterization?: number | undefined | null
}
export function pngQuantize(input: Buffer, options?: PngQuantOptions | undefined | null): Buffer
export const enum Ident {
None = 0,
Two = 2,
Four = 4,
Tab = 5
}
export interface SvgMinOptions {
idPrefix?: string | undefined | null
useSingleQuote?: boolean | undefined | null
indent?: Ident | undefined | null
attributesIndent?: Ident | undefined | null
}
export function svgMin(input: string | Buffer, options?: SvgMinOptions | undefined | null): string
export function losslessWebpFromPng(input: Buffer): Buffer

View File

@ -13,28 +13,43 @@ function isMusl() {
try {
return readFileSync('/usr/bin/ldd', 'utf8').includes('musl')
} catch (e) {
return false
return true
}
} else {
const { glibcVersionRuntime } = process.report.getReport().header
return !Boolean(glibcVersionRuntime)
return !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
switch (arch) {
case 'arm64':
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 'arm':
localFileExisted = existsSync(join(__dirname, 'image.android-arm-eabi.node'))
try {
if (localFileExisted) {
nativeBinding = require('./image.android-arm-eabi.node')
} else {
nativeBinding = require('@napi-rs/image-android-arm-eabi')
}
} catch (e) {
loadError = e
}
break
default:
throw new Error(`Unsupported architecture on Android ${arch}`)
}
break
case 'win32':
@ -221,10 +236,9 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
const { losslessCompressPng, compressJpeg, pngQuantize, Ident, svgMin } = nativeBinding
const { losslessCompressPng, compressJpeg, pngQuantize, losslessWebpFromPng } = nativeBinding
module.exports.losslessCompressPng = losslessCompressPng
module.exports.compressJpeg = compressJpeg
module.exports.pngQuantize = pngQuantize
module.exports.Ident = Ident
module.exports.svgMin = svgMin
module.exports.losslessWebpFromPng = losslessWebpFromPng

View File

@ -262,75 +262,27 @@ pub fn png_quantize(input: Buffer, options: Option<PngQuantOptions>) -> Result<B
}
#[napi]
pub enum Ident {
None,
Two = 2,
Four = 4,
Tab,
}
#[napi(object)]
pub struct SvgMinOptions {
pub id_prefix: Option<String>,
pub use_single_quote: Option<bool>,
pub indent: Option<Ident>,
pub attributes_indent: Option<Ident>,
}
#[inline(always)]
fn ident_to_xml_ident(id: &Ident) -> xmlwriter::Indent {
match id {
&Ident::None => xmlwriter::Indent::None,
&Ident::Two => xmlwriter::Indent::Spaces(2),
&Ident::Four => xmlwriter::Indent::Spaces(4),
&Ident::Tab => xmlwriter::Indent::Tabs,
}
}
#[napi]
pub fn svg_min(input: Either<String, Buffer>, options: Option<SvgMinOptions>) -> Result<String> {
let (tree, len) = match &input {
Either::A(s) => {
usvg::Tree::from_str(s.as_str(), &usvg::Options::default().to_ref()).map(|t| (t, s.len()))
}
Either::B(b) => {
usvg::Tree::from_data(b.as_ref(), &usvg::Options::default().to_ref()).map(|t| (t, b.len()))
}
}
.map_err(|err| {
pub unsafe fn lossless_webp_from_png(env: Env, input: Buffer) -> Result<JsBuffer> {
let image = lodepng::decode32(input).map_err(|err| {
Error::new(
Status::InvalidArg,
format!("Parse svg from input data failed {}", err),
format!("Decode png from input buffer failed {}", err),
)
})?;
let options = options.unwrap_or(SvgMinOptions {
id_prefix: None,
use_single_quote: Some(true),
indent: Some(Ident::None),
attributes_indent: Some(Ident::None),
});
let result = tree.to_string(&usvg::XmlOptions {
id_prefix: options.id_prefix,
writer_opts: xmlwriter::Options {
use_single_quote: options.use_single_quote.unwrap_or(true),
indent: options
.indent
.as_ref()
.map(ident_to_xml_ident)
.unwrap_or(xmlwriter::Indent::None),
attributes_indent: options
.attributes_indent
.as_ref()
.map(ident_to_xml_ident)
.unwrap_or(xmlwriter::Indent::None),
},
});
if result.len() < len {
Ok(result)
} else {
Ok(match input {
Either::A(a) => a,
Either::B(b) => unsafe { String::from_utf8_unchecked(b.to_vec()) },
let mut out_buf = std::ptr::null_mut();
let width = image.width;
let height = image.height;
let stride = width as i32 * 4;
let len = libwebp_sys::WebPEncodeLosslessRGBA(
image.buffer.as_ptr() as *const _,
width as i32,
height as i32,
stride,
&mut out_buf,
);
env
.create_buffer_with_borrowed_data(out_buf, len, out_buf, |raw, _env| {
Vec::from_raw_parts(raw, len, len);
})
}
.map(|v| v.into_raw())
}

589
yarn.lock

File diff suppressed because it is too large Load Diff