Compare commits
21 Commits
v0.33.0-al
...
v0.33.0-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef32dccb83 | ||
|
|
e78200cc84 | ||
|
|
3f7313d031 | ||
|
|
139e4b9f65 | ||
|
|
9680f00ddd | ||
|
|
0b0ebfe6f8 | ||
|
|
5aadb8294c | ||
|
|
ace1681886 | ||
|
|
75ef61a958 | ||
|
|
2dba5b5451 | ||
|
|
a8f68ba7f0 | ||
|
|
475bf16b09 | ||
|
|
67f49a8dcd | ||
|
|
074004392c | ||
|
|
8c5a493f68 | ||
|
|
cccd1635c5 | ||
|
|
1915c1387e | ||
|
|
4a37a27cca | ||
|
|
ab8a4ed532 | ||
|
|
12fd512b83 | ||
|
|
239435a6dc |
33
.github/workflows/ci.yml
vendored
@@ -161,6 +161,39 @@ jobs:
|
||||
npm install --ignore-scripts
|
||||
npx mocha --no-config --spec=test/unit/io.js --timeout=30000
|
||||
[[ -n $prebuild_upload ]] && cd src && ln -s ../package.json && npx prebuild || true
|
||||
github-runner-emscripten:
|
||||
permissions:
|
||||
contents: write
|
||||
name: wasm32 - prebuild
|
||||
runs-on: ubuntu-22.04
|
||||
container: "emscripten/emsdk:3.1.48"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Dependencies
|
||||
run: apt-get update && apt-get install -y pkg-config
|
||||
- name: Dependencies (Node.js)
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "20"
|
||||
- name: Install
|
||||
run: emmake npm install --build-from-source
|
||||
- name: Test
|
||||
run: emmake npm test
|
||||
- name: Test packaging
|
||||
run: |
|
||||
emmake npm run package-from-local-build
|
||||
npm pkg set "optionalDependencies.@img/sharp-wasm32=file:./npm/wasm32"
|
||||
npm run clean
|
||||
rm -r node_modules/@img/sharp-linux-x64
|
||||
npm install --cpu=wasm32
|
||||
npm test
|
||||
- name: Prebuild
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
env:
|
||||
npm_config_nodedir: emscripten
|
||||
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: cd src && ln -s ../package.json && emmake npx prebuild --platform=emscripten --arch=wasm32 --strip=0
|
||||
macstadium-runner:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
4
.github/workflows/npm.yml
vendored
@@ -112,10 +112,8 @@ jobs:
|
||||
with:
|
||||
path: release.mjs
|
||||
contents: |
|
||||
import { createRequire } from 'node:module';
|
||||
import { deepStrictEqual } from 'node:assert';
|
||||
const require = createRequire(import.meta.url);
|
||||
const sharp = require('sharp');
|
||||
import sharp from 'sharp';
|
||||
deepStrictEqual(['.jpg', '.jpeg', '.jpe'], sharp.format.jpeg.input.fileSuffix);
|
||||
|
||||
- name: Run with Node.js + npm
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## tint
|
||||
> tint(rgb) ⇒ <code>Sharp</code>
|
||||
> tint(tint) ⇒ <code>Sharp</code>
|
||||
|
||||
Tint the image using the provided chroma while preserving the image luminance.
|
||||
Tint the image using the provided colour.
|
||||
An alpha channel may be present and will be unchanged by the operation.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ An alpha channel may be present and will be unchanged by the operation.
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| rgb | <code>string</code> \| <code>Object</code> | parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values. |
|
||||
| tint | <code>string</code> \| <code>Object</code> | Parsed by the [color](https://www.npmjs.org/package/color) module. |
|
||||
|
||||
**Example**
|
||||
```js
|
||||
|
||||
@@ -111,17 +111,157 @@ await sharp(pixelArray, { raw: { width, height, channels } })
|
||||
```
|
||||
|
||||
|
||||
## keepExif
|
||||
> keepExif() ⇒ <code>Sharp</code>
|
||||
|
||||
Keep all EXIF metadata from the input image in the output image.
|
||||
|
||||
EXIF metadata is unsupported for TIFF output.
|
||||
|
||||
|
||||
**Since**: 0.33.0
|
||||
**Example**
|
||||
```js
|
||||
const outputWithExif = await sharp(inputWithExif)
|
||||
.keepExif()
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
|
||||
## withExif
|
||||
> withExif(exif) ⇒ <code>Sharp</code>
|
||||
|
||||
Set EXIF metadata in the output image, ignoring any EXIF in the input image.
|
||||
|
||||
|
||||
**Throws**:
|
||||
|
||||
- <code>Error</code> Invalid parameters
|
||||
|
||||
**Since**: 0.33.0
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| exif | <code>Object.<string, Object.<string, string>></code> | Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. |
|
||||
|
||||
**Example**
|
||||
```js
|
||||
const dataWithExif = await sharp(input)
|
||||
.withExif({
|
||||
IFD0: {
|
||||
Copyright: 'The National Gallery'
|
||||
},
|
||||
IFD3: {
|
||||
GPSLatitudeRef: 'N',
|
||||
GPSLatitude: '51/1 30/1 3230/100',
|
||||
GPSLongitudeRef: 'W',
|
||||
GPSLongitude: '0/1 7/1 4366/100'
|
||||
}
|
||||
})
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
|
||||
## withExifMerge
|
||||
> withExifMerge(exif) ⇒ <code>Sharp</code>
|
||||
|
||||
Update EXIF metadata from the input image in the output image.
|
||||
|
||||
|
||||
**Throws**:
|
||||
|
||||
- <code>Error</code> Invalid parameters
|
||||
|
||||
**Since**: 0.33.0
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| exif | <code>Object.<string, Object.<string, string>></code> | Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. |
|
||||
|
||||
**Example**
|
||||
```js
|
||||
const dataWithMergedExif = await sharp(inputWithExif)
|
||||
.withExifMerge({
|
||||
IFD0: {
|
||||
Copyright: 'The National Gallery'
|
||||
}
|
||||
})
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
|
||||
## keepIccProfile
|
||||
> keepIccProfile() ⇒ <code>Sharp</code>
|
||||
|
||||
Keep ICC profile from the input image in the output image.
|
||||
|
||||
Where necessary, will attempt to convert the output colour space to match the profile.
|
||||
|
||||
|
||||
**Since**: 0.33.0
|
||||
**Example**
|
||||
```js
|
||||
const outputWithIccProfile = await sharp(inputWithIccProfile)
|
||||
.keepIccProfile()
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
|
||||
## withIccProfile
|
||||
> withIccProfile(icc, [options]) ⇒ <code>Sharp</code>
|
||||
|
||||
Transform using an ICC profile and attach to the output image.
|
||||
|
||||
This can either be an absolute filesystem path or
|
||||
built-in profile name (`srgb`, `p3`, `cmyk`).
|
||||
|
||||
|
||||
**Throws**:
|
||||
|
||||
- <code>Error</code> Invalid parameters
|
||||
|
||||
**Since**: 0.33.0
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| icc | <code>string</code> | | Absolute filesystem path to output ICC profile or built-in profile name (srgb, p3, cmyk). |
|
||||
| [options] | <code>Object</code> | | |
|
||||
| [options.attach] | <code>number</code> | <code>true</code> | Should the ICC profile be included in the output image metadata? |
|
||||
|
||||
**Example**
|
||||
```js
|
||||
const outputWithP3 = await sharp(input)
|
||||
.withIccProfile('p3')
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
|
||||
## keepMetadata
|
||||
> keepMetadata() ⇒ <code>Sharp</code>
|
||||
|
||||
Keep all metadata (EXIF, ICC, XMP, IPTC) from the input image in the output image.
|
||||
|
||||
The default behaviour, when `keepMetadata` is not used, is to convert to the device-independent
|
||||
sRGB colour space and strip all metadata, including the removal of any ICC profile.
|
||||
|
||||
|
||||
**Since**: 0.33.0
|
||||
**Example**
|
||||
```js
|
||||
const outputWithMetadata = await sharp(inputWithMetadata)
|
||||
.keepMetadata()
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
|
||||
## withMetadata
|
||||
> withMetadata([options]) ⇒ <code>Sharp</code>
|
||||
|
||||
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
||||
This will also convert to and add a web-friendly sRGB ICC profile if appropriate,
|
||||
unless a custom output profile is provided.
|
||||
Keep most metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
||||
|
||||
The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
|
||||
sRGB colour space and strip all metadata, including the removal of any ICC profile.
|
||||
This will also convert to and add a web-friendly sRGB ICC profile if appropriate.
|
||||
|
||||
EXIF metadata is unsupported for TIFF output.
|
||||
Allows orientation and density to be set or updated.
|
||||
|
||||
|
||||
**Throws**:
|
||||
@@ -129,38 +269,16 @@ EXIF metadata is unsupported for TIFF output.
|
||||
- <code>Error</code> Invalid parameters
|
||||
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [options] | <code>Object</code> | | |
|
||||
| [options.orientation] | <code>number</code> | | value between 1 and 8, used to update the EXIF `Orientation` tag. |
|
||||
| [options.icc] | <code>string</code> | <code>"'srgb'"</code> | Filesystem path to output ICC profile, relative to `process.cwd()`, defaults to built-in sRGB. |
|
||||
| [options.exif] | <code>Object.<Object></code> | <code>{}</code> | Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. |
|
||||
| [options.density] | <code>number</code> | | Number of pixels per inch (DPI). |
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [options] | <code>Object</code> | |
|
||||
| [options.orientation] | <code>number</code> | Used to update the EXIF `Orientation` tag, integer between 1 and 8. |
|
||||
| [options.density] | <code>number</code> | Number of pixels per inch (DPI). |
|
||||
|
||||
**Example**
|
||||
```js
|
||||
sharp('input.jpg')
|
||||
const outputSrgbWithMetadata = await sharp(inputRgbWithMetadata)
|
||||
.withMetadata()
|
||||
.toFile('output-with-metadata.jpg')
|
||||
.then(info => { ... });
|
||||
```
|
||||
**Example**
|
||||
```js
|
||||
// Set output EXIF metadata
|
||||
const data = await sharp(input)
|
||||
.withMetadata({
|
||||
exif: {
|
||||
IFD0: {
|
||||
Copyright: 'The National Gallery'
|
||||
},
|
||||
IFD3: {
|
||||
GPSLatitudeRef: 'N',
|
||||
GPSLatitude: '51/1 30/1 3230/100',
|
||||
GPSLongitudeRef: 'W',
|
||||
GPSLongitude: '0/1 7/1 4366/100'
|
||||
}
|
||||
}
|
||||
})
|
||||
.toBuffer();
|
||||
```
|
||||
**Example**
|
||||
|
||||
@@ -14,9 +14,18 @@ Requires libvips v8.15.0
|
||||
|
||||
* Remove `sharp.vendor`.
|
||||
|
||||
* Partially deprecate `withMetadata()`, use `withExif()` and `withIccProfile()`.
|
||||
|
||||
* Add experimental support for WebAssembly-based runtimes.
|
||||
[@RReverser](https://github.com/RReverser)
|
||||
|
||||
* Options for `trim` operation must be an Object, add new `lineArt` option.
|
||||
[#2363](https://github.com/lovell/sharp/issues/2363)
|
||||
|
||||
* Improve luminance of `tint` operation with weighting function.
|
||||
[#3338](https://github.com/lovell/sharp/issues/3338)
|
||||
[@jcupitt](https://github.com/jcupitt)
|
||||
|
||||
* Ensure all `Error` objects contain a `stack` property.
|
||||
[#3653](https://github.com/lovell/sharp/issues/3653)
|
||||
|
||||
@@ -34,6 +43,9 @@ Requires libvips v8.15.0
|
||||
[#3823](https://github.com/lovell/sharp/pull/3823)
|
||||
[@uhthomas](https://github.com/uhthomas)
|
||||
|
||||
* Add more fine-grained control over output metadata.
|
||||
[#3824](https://github.com/lovell/sharp/issues/3824)
|
||||
|
||||
* Ensure multi-page extract remains sequential.
|
||||
[#3837](https://github.com/lovell/sharp/issues/3837)
|
||||
|
||||
|
||||
@@ -278,3 +278,6 @@ GitHub: https://github.com/bianjunjie1981
|
||||
|
||||
Name: Dennis Beatty
|
||||
GitHub: https://github.com/dnsbty
|
||||
|
||||
Name: Ingvar Stepanyan
|
||||
GitHub: https://github.com/RReverser
|
||||
|
||||
@@ -78,6 +78,15 @@ For cross-compiling, the `--platform`, `--arch` and `--libc` npm flags
|
||||
(or the `npm_config_platform`, `npm_config_arch` and `npm_config_libc` environment variables)
|
||||
can be used to configure the target environment.
|
||||
|
||||
## WebAssembly
|
||||
|
||||
Experimental support is provided for runtime environments that provide
|
||||
multi-threaded Wasm via Workers.
|
||||
|
||||
```sh
|
||||
npm install --cpu=wasm32 sharp
|
||||
```
|
||||
|
||||
## FreeBSD
|
||||
|
||||
The `vips` package must be installed before `npm install` is run.
|
||||
@@ -117,6 +126,16 @@ depending on the chosen architecture.
|
||||
When building your deployment package on a machine that differs from the target architecture,
|
||||
you will need to install either `@img/sharp-linux-x64` or `@img/sharp-linux-arm64` package.
|
||||
|
||||
```sh
|
||||
npm install --os=linux --cpu=x64 sharp
|
||||
```
|
||||
|
||||
```sh
|
||||
npm install --os=linux --cpu=arm64 sharp
|
||||
```
|
||||
|
||||
When using npm 9 or earlier, this can be achieved using the following:
|
||||
|
||||
```sh
|
||||
npm install --force @img/sharp-linux-x64
|
||||
```
|
||||
@@ -174,7 +193,7 @@ custom:
|
||||
- sharp
|
||||
packagerOptions:
|
||||
scripts:
|
||||
- npm install --force @img/sharp-linux-x64
|
||||
- npm install --os=linux --cpu=x64 sharp
|
||||
```
|
||||
|
||||
## TypeScript
|
||||
@@ -213,19 +232,6 @@ use the `FONTCONFIG_PATH` environment variable to point to a custom location.
|
||||
|
||||
Embedded SVG fonts are unsupported.
|
||||
|
||||
## Worker threads
|
||||
|
||||
On some platforms, including glibc-based Linux,
|
||||
the main thread must call `require('sharp')`
|
||||
_before_ worker threads are created.
|
||||
This is to ensure shared libraries remain loaded in memory
|
||||
until after all threads are complete.
|
||||
|
||||
Without this, the following error may occur:
|
||||
```
|
||||
Module did not self-register
|
||||
```
|
||||
|
||||
## Known conflicts
|
||||
|
||||
### Canvas and Windows
|
||||
|
||||
@@ -19,7 +19,7 @@ const colourspace = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Tint the image using the provided chroma while preserving the image luminance.
|
||||
* Tint the image using the provided colour.
|
||||
* An alpha channel may be present and will be unchanged by the operation.
|
||||
*
|
||||
* @example
|
||||
@@ -27,14 +27,12 @@ const colourspace = {
|
||||
* .tint({ r: 255, g: 240, b: 16 })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {string|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
|
||||
* @param {string|Object} tint - Parsed by the [color](https://www.npmjs.org/package/color) module.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameter
|
||||
*/
|
||||
function tint (rgb) {
|
||||
const colour = color(rgb);
|
||||
this.options.tintA = colour.a();
|
||||
this.options.tintB = colour.b();
|
||||
function tint (tint) {
|
||||
this._setBackgroundColourOption('tint', tint);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -212,8 +212,7 @@ const Sharp = function (input, options) {
|
||||
kernel: 'lanczos3',
|
||||
fastShrinkOnLoad: true,
|
||||
// operations
|
||||
tintA: 128,
|
||||
tintB: 128,
|
||||
tint: [-1, 0, 0, 0],
|
||||
flatten: false,
|
||||
flattenBackground: [0, 0, 0],
|
||||
unflatten: false,
|
||||
@@ -258,11 +257,12 @@ const Sharp = function (input, options) {
|
||||
fileOut: '',
|
||||
formatOut: 'input',
|
||||
streamOut: false,
|
||||
withMetadata: false,
|
||||
keepMetadata: 0,
|
||||
withMetadataOrientation: -1,
|
||||
withMetadataDensity: 0,
|
||||
withMetadataIcc: '',
|
||||
withMetadataStrs: {},
|
||||
withIccProfile: '',
|
||||
withExif: {},
|
||||
withExifMerge: true,
|
||||
resolveWithObject: false,
|
||||
// output format
|
||||
jpegQuality: 80,
|
||||
|
||||
79
lib/index.d.ts
vendored
@@ -239,12 +239,12 @@ declare namespace sharp {
|
||||
//#region Color functions
|
||||
|
||||
/**
|
||||
* Tint the image using the provided chroma while preserving the image luminance.
|
||||
* Tint the image using the provided colour.
|
||||
* An alpha channel may be present and will be unchanged by the operation.
|
||||
* @param rgb Parsed by the color module to extract chroma values.
|
||||
* @param tint Parsed by the color module.
|
||||
* @returns A sharp instance that can be used to chain operations
|
||||
*/
|
||||
tint(rgb: Color): Sharp;
|
||||
tint(tint: Color): Sharp;
|
||||
|
||||
/**
|
||||
* Convert to 8-bit greyscale; 256 shades of grey.
|
||||
@@ -633,6 +633,43 @@ declare namespace sharp {
|
||||
*/
|
||||
toBuffer(options: { resolveWithObject: true }): Promise<{ data: Buffer; info: OutputInfo }>;
|
||||
|
||||
/**
|
||||
* Keep all EXIF metadata from the input image in the output image.
|
||||
* EXIF metadata is unsupported for TIFF output.
|
||||
* @returns A sharp instance that can be used to chain operations
|
||||
*/
|
||||
keepExif(): Sharp;
|
||||
|
||||
/**
|
||||
* Set EXIF metadata in the output image, ignoring any EXIF in the input image.
|
||||
* @param {Exif} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||
* @returns A sharp instance that can be used to chain operations
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
withExif(exif: Exif): Sharp;
|
||||
|
||||
/**
|
||||
* Update EXIF metadata from the input image in the output image.
|
||||
* @param {Exif} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||
* @returns A sharp instance that can be used to chain operations
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
withExifMerge(exif: Exif): Sharp;
|
||||
|
||||
/**
|
||||
* Keep ICC profile from the input image in the output image where possible.
|
||||
* @returns A sharp instance that can be used to chain operations
|
||||
*/
|
||||
keepIccProfile(): Sharp;
|
||||
|
||||
/**
|
||||
* Transform using an ICC profile and attach to the output image.
|
||||
* @param {string} icc - Absolute filesystem path to output ICC profile or built-in profile name (srgb, p3, cmyk).
|
||||
* @returns A sharp instance that can be used to chain operations
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
withIccProfile(icc: string, options?: WithIccProfileOptions): Sharp;
|
||||
|
||||
/**
|
||||
* Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
||||
* The default behaviour, when withMetadata is not used, is to strip all metadata and convert to the device-independent sRGB colour space.
|
||||
@@ -640,7 +677,7 @@ declare namespace sharp {
|
||||
* @param withMetadata
|
||||
* @throws {Error} Invalid parameters.
|
||||
*/
|
||||
withMetadata(withMetadata?: boolean | WriteableMetadata): Sharp;
|
||||
withMetadata(withMetadata?: WriteableMetadata): Sharp;
|
||||
|
||||
/**
|
||||
* Use these JPEG options for output image.
|
||||
@@ -978,15 +1015,32 @@ declare namespace sharp {
|
||||
wrap?: TextWrap;
|
||||
}
|
||||
|
||||
interface ExifDir {
|
||||
[k: string]: string;
|
||||
}
|
||||
|
||||
interface Exif {
|
||||
'IFD0'?: ExifDir;
|
||||
'IFD1'?: ExifDir;
|
||||
'IFD2'?: ExifDir;
|
||||
'IFD3'?: ExifDir;
|
||||
}
|
||||
|
||||
interface WriteableMetadata {
|
||||
/** Value between 1 and 8, used to update the EXIF Orientation tag. */
|
||||
orientation?: number | undefined;
|
||||
/** Filesystem path to output ICC profile, defaults to sRGB. */
|
||||
icc?: string | undefined;
|
||||
/** Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. (optional, default {}) */
|
||||
exif?: Record<string, any> | undefined;
|
||||
/** Number of pixels per inch (DPI) */
|
||||
density?: number | undefined;
|
||||
/** Value between 1 and 8, used to update the EXIF Orientation tag. */
|
||||
orientation?: number | undefined;
|
||||
/**
|
||||
* Filesystem path to output ICC profile, defaults to sRGB.
|
||||
* @deprecated Use `withIccProfile()` instead.
|
||||
*/
|
||||
icc?: string | undefined;
|
||||
/**
|
||||
* Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||
* @deprecated Use `withExif()` or `withExifMerge()` instead.
|
||||
*/
|
||||
exif?: Exif | undefined;
|
||||
}
|
||||
|
||||
interface Metadata {
|
||||
@@ -1096,6 +1150,11 @@ declare namespace sharp {
|
||||
force?: boolean | undefined;
|
||||
}
|
||||
|
||||
interface WithIccProfileOptions {
|
||||
/** Should the ICC profile be included in the output image metadata? (optional, default true) */
|
||||
attach?: boolean | undefined;
|
||||
}
|
||||
|
||||
interface JpegOptions extends OutputOptions {
|
||||
/** Quality, integer 1-100 (optional, default 80) */
|
||||
quality?: number | undefined;
|
||||
|
||||
@@ -16,7 +16,7 @@ const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).versio
|
||||
|
||||
const prebuiltPlatforms = [
|
||||
'darwin-arm64', 'darwin-x64',
|
||||
'linux-arm', 'linux-arm64', 'linux-x64',
|
||||
'linux-arm', 'linux-arm64', 'linux-s390x', 'linux-x64',
|
||||
'linuxmusl-arm64', 'linuxmusl-x64',
|
||||
'win32-ia32', 'win32-x64'
|
||||
];
|
||||
@@ -41,15 +41,23 @@ const runtimePlatformArch = () => `${process.platform}${runtimeLibc()}-${process
|
||||
|
||||
/* istanbul ignore next */
|
||||
const buildPlatformArch = () => {
|
||||
if (isEmscripten()) {
|
||||
return 'wasm32';
|
||||
}
|
||||
/* eslint camelcase: ["error", { allow: ["^npm_config_"] }] */
|
||||
const { npm_config_arch, npm_config_platform, npm_config_libc } = process.env;
|
||||
return `${npm_config_platform || process.platform}${npm_config_libc || runtimeLibc()}-${npm_config_arch || process.arch}`;
|
||||
const libc = typeof npm_config_libc === 'string' ? npm_config_libc : runtimeLibc();
|
||||
return `${npm_config_platform || process.platform}${libc}-${npm_config_arch || process.arch}`;
|
||||
};
|
||||
|
||||
const buildSharpLibvipsIncludeDir = () => {
|
||||
try {
|
||||
return require('@img/sharp-libvips-dev/include');
|
||||
} catch {}
|
||||
return require(`@img/sharp-libvips-dev-${buildPlatformArch()}/include`);
|
||||
} catch {
|
||||
try {
|
||||
return require('@img/sharp-libvips-dev/include');
|
||||
} catch {}
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
return '';
|
||||
};
|
||||
@@ -64,12 +72,22 @@ const buildSharpLibvipsCPlusPlusDir = () => {
|
||||
|
||||
const buildSharpLibvipsLibDir = () => {
|
||||
try {
|
||||
return require(`@img/sharp-libvips-${buildPlatformArch()}/lib`);
|
||||
} catch {}
|
||||
return require(`@img/sharp-libvips-dev-${buildPlatformArch()}/lib`);
|
||||
} catch {
|
||||
try {
|
||||
return require(`@img/sharp-libvips-${buildPlatformArch()}/lib`);
|
||||
} catch {}
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
return '';
|
||||
};
|
||||
|
||||
/* istanbul ignore next */
|
||||
const isEmscripten = () => {
|
||||
const { CC } = process.env;
|
||||
return Boolean(CC && CC.endsWith('/emcc'));
|
||||
};
|
||||
|
||||
const isRosetta = () => {
|
||||
/* istanbul ignore next */
|
||||
if (process.platform === 'darwin' && process.arch === 'x64') {
|
||||
@@ -81,7 +99,7 @@ const isRosetta = () => {
|
||||
|
||||
/* istanbul ignore next */
|
||||
const spawnRebuild = () =>
|
||||
spawnSync('node-gyp rebuild --directory=src', {
|
||||
spawnSync(`node-gyp rebuild --directory=src ${isEmscripten() ? '--nodedir=emscripten' : ''}`, {
|
||||
...spawnSyncOptions,
|
||||
stdio: 'inherit'
|
||||
}).status;
|
||||
|
||||
231
lib/output.js
@@ -163,39 +163,185 @@ function toBuffer (options, callback) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
||||
* This will also convert to and add a web-friendly sRGB ICC profile if appropriate,
|
||||
* unless a custom output profile is provided.
|
||||
*
|
||||
* The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
|
||||
* sRGB colour space and strip all metadata, including the removal of any ICC profile.
|
||||
* Keep all EXIF metadata from the input image in the output image.
|
||||
*
|
||||
* EXIF metadata is unsupported for TIFF output.
|
||||
*
|
||||
* @example
|
||||
* sharp('input.jpg')
|
||||
* .withMetadata()
|
||||
* .toFile('output-with-metadata.jpg')
|
||||
* .then(info => { ... });
|
||||
* @since 0.33.0
|
||||
*
|
||||
* @example
|
||||
* // Set output EXIF metadata
|
||||
* const data = await sharp(input)
|
||||
* .withMetadata({
|
||||
* exif: {
|
||||
* IFD0: {
|
||||
* Copyright: 'The National Gallery'
|
||||
* },
|
||||
* IFD3: {
|
||||
* GPSLatitudeRef: 'N',
|
||||
* GPSLatitude: '51/1 30/1 3230/100',
|
||||
* GPSLongitudeRef: 'W',
|
||||
* GPSLongitude: '0/1 7/1 4366/100'
|
||||
* }
|
||||
* const outputWithExif = await sharp(inputWithExif)
|
||||
* .keepExif()
|
||||
* .toBuffer();
|
||||
*
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function keepExif () {
|
||||
this.options.keepMetadata |= 0b00001;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set EXIF metadata in the output image, ignoring any EXIF in the input image.
|
||||
*
|
||||
* @since 0.33.0
|
||||
*
|
||||
* @example
|
||||
* const dataWithExif = await sharp(input)
|
||||
* .withExif({
|
||||
* IFD0: {
|
||||
* Copyright: 'The National Gallery'
|
||||
* },
|
||||
* IFD3: {
|
||||
* GPSLatitudeRef: 'N',
|
||||
* GPSLatitude: '51/1 30/1 3230/100',
|
||||
* GPSLongitudeRef: 'W',
|
||||
* GPSLongitude: '0/1 7/1 4366/100'
|
||||
* }
|
||||
* })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {Object<string, Object<string, string>>} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function withExif (exif) {
|
||||
if (is.object(exif)) {
|
||||
for (const [ifd, entries] of Object.entries(exif)) {
|
||||
if (is.object(entries)) {
|
||||
for (const [k, v] of Object.entries(entries)) {
|
||||
if (is.string(v)) {
|
||||
this.options.withExif[`exif-${ifd.toLowerCase()}-${k}`] = v;
|
||||
} else {
|
||||
throw is.invalidParameterError(`${ifd}.${k}`, 'string', v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw is.invalidParameterError(ifd, 'object', entries);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw is.invalidParameterError('exif', 'object', exif);
|
||||
}
|
||||
this.options.withExifMerge = false;
|
||||
return this.keepExif();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update EXIF metadata from the input image in the output image.
|
||||
*
|
||||
* @since 0.33.0
|
||||
*
|
||||
* @example
|
||||
* const dataWithMergedExif = await sharp(inputWithExif)
|
||||
* .withExifMerge({
|
||||
* IFD0: {
|
||||
* Copyright: 'The National Gallery'
|
||||
* }
|
||||
* })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {Object<string, Object<string, string>>} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function withExifMerge (exif) {
|
||||
this.withExif(exif);
|
||||
this.options.withExifMerge = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep ICC profile from the input image in the output image.
|
||||
*
|
||||
* Where necessary, will attempt to convert the output colour space to match the profile.
|
||||
*
|
||||
* @since 0.33.0
|
||||
*
|
||||
* @example
|
||||
* const outputWithIccProfile = await sharp(inputWithIccProfile)
|
||||
* .keepIccProfile()
|
||||
* .toBuffer();
|
||||
*
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function keepIccProfile () {
|
||||
this.options.keepMetadata |= 0b01000;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform using an ICC profile and attach to the output image.
|
||||
*
|
||||
* This can either be an absolute filesystem path or
|
||||
* built-in profile name (`srgb`, `p3`, `cmyk`).
|
||||
*
|
||||
* @since 0.33.0
|
||||
*
|
||||
* @example
|
||||
* const outputWithP3 = await sharp(input)
|
||||
* .withIccProfile('p3')
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {string} icc - Absolute filesystem path to output ICC profile or built-in profile name (srgb, p3, cmyk).
|
||||
* @param {Object} [options]
|
||||
* @param {number} [options.attach=true] Should the ICC profile be included in the output image metadata?
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function withIccProfile (icc, options) {
|
||||
if (is.string(icc)) {
|
||||
this.options.withIccProfile = icc;
|
||||
} else {
|
||||
throw is.invalidParameterError('icc', 'string', icc);
|
||||
}
|
||||
this.keepIccProfile();
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.attach)) {
|
||||
if (is.bool(options.attach)) {
|
||||
if (!options.attach) {
|
||||
this.options.keepMetadata &= ~0b01000;
|
||||
}
|
||||
} else {
|
||||
throw is.invalidParameterError('attach', 'boolean', options.attach);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep all metadata (EXIF, ICC, XMP, IPTC) from the input image in the output image.
|
||||
*
|
||||
* The default behaviour, when `keepMetadata` is not used, is to convert to the device-independent
|
||||
* sRGB colour space and strip all metadata, including the removal of any ICC profile.
|
||||
*
|
||||
* @since 0.33.0
|
||||
*
|
||||
* @example
|
||||
* const outputWithMetadata = await sharp(inputWithMetadata)
|
||||
* .keepMetadata()
|
||||
* .toBuffer();
|
||||
*
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function keepMetadata () {
|
||||
this.options.keepMetadata = 0b11111;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep most metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
||||
*
|
||||
* This will also convert to and add a web-friendly sRGB ICC profile if appropriate.
|
||||
*
|
||||
* Allows orientation and density to be set or updated.
|
||||
*
|
||||
* @example
|
||||
* const outputSrgbWithMetadata = await sharp(inputRgbWithMetadata)
|
||||
* .withMetadata()
|
||||
* .toBuffer();
|
||||
*
|
||||
* @example
|
||||
* // Set output metadata to 96 DPI
|
||||
* const data = await sharp(input)
|
||||
@@ -203,15 +349,14 @@ function toBuffer (options, callback) {
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
|
||||
* @param {string} [options.icc='srgb'] Filesystem path to output ICC profile, relative to `process.cwd()`, defaults to built-in sRGB.
|
||||
* @param {Object<Object>} [options.exif={}] Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||
* @param {number} [options.orientation] Used to update the EXIF `Orientation` tag, integer between 1 and 8.
|
||||
* @param {number} [options.density] Number of pixels per inch (DPI).
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function withMetadata (options) {
|
||||
this.options.withMetadata = is.bool(options) ? options : true;
|
||||
this.keepMetadata();
|
||||
this.withIccProfile('srgb');
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.orientation)) {
|
||||
if (is.integer(options.orientation) && is.inRange(options.orientation, 1, 8)) {
|
||||
@@ -228,30 +373,10 @@ function withMetadata (options) {
|
||||
}
|
||||
}
|
||||
if (is.defined(options.icc)) {
|
||||
if (is.string(options.icc)) {
|
||||
this.options.withMetadataIcc = options.icc;
|
||||
} else {
|
||||
throw is.invalidParameterError('icc', 'string filesystem path to ICC profile', options.icc);
|
||||
}
|
||||
this.withIccProfile(options.icc);
|
||||
}
|
||||
if (is.defined(options.exif)) {
|
||||
if (is.object(options.exif)) {
|
||||
for (const [ifd, entries] of Object.entries(options.exif)) {
|
||||
if (is.object(entries)) {
|
||||
for (const [k, v] of Object.entries(entries)) {
|
||||
if (is.string(v)) {
|
||||
this.options.withMetadataStrs[`exif-${ifd.toLowerCase()}-${k}`] = v;
|
||||
} else {
|
||||
throw is.invalidParameterError(`exif.${ifd}.${k}`, 'string', v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw is.invalidParameterError(`exif.${ifd}`, 'object', entries);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw is.invalidParameterError('exif', 'object', options.exif);
|
||||
}
|
||||
this.withExifMerge(options.exif);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
@@ -1407,6 +1532,12 @@ module.exports = function (Sharp) {
|
||||
// Public
|
||||
toFile,
|
||||
toBuffer,
|
||||
keepExif,
|
||||
withExif,
|
||||
withExifMerge,
|
||||
keepIccProfile,
|
||||
withIccProfile,
|
||||
keepMetadata,
|
||||
withMetadata,
|
||||
toFormat,
|
||||
jpeg,
|
||||
|
||||
125
lib/sharp.js
@@ -9,61 +9,78 @@ const { familySync, versionSync } = require('detect-libc');
|
||||
|
||||
const { runtimePlatformArch, prebuiltPlatforms, minimumLibvipsVersion } = require('./libvips');
|
||||
const runtimePlatform = runtimePlatformArch();
|
||||
const [isLinux, isMacOs, isWindows] = ['linux', 'darwin', 'win32'].map(os => runtimePlatform.startsWith(os));
|
||||
|
||||
/* istanbul ignore next */
|
||||
try {
|
||||
// Check for local build
|
||||
module.exports = require(`../src/build/Release/sharp-${runtimePlatform}.node`);
|
||||
} catch (errLocal) {
|
||||
const paths = [
|
||||
`../src/build/Release/sharp-${runtimePlatform}.node`,
|
||||
'../src/build/Release/sharp-wasm32.node',
|
||||
`@img/sharp-${runtimePlatform}/sharp.node`,
|
||||
'@img/sharp-wasm32/sharp.node'
|
||||
];
|
||||
|
||||
let sharp;
|
||||
const errors = [];
|
||||
for (const path of paths) {
|
||||
try {
|
||||
// Check for runtime package
|
||||
module.exports = require(`@img/sharp-${runtimePlatform}/sharp.node`);
|
||||
} catch (errPackage) {
|
||||
const help = ['Could not load the "sharp" module at runtime'];
|
||||
if (errLocal.code !== 'MODULE_NOT_FOUND') {
|
||||
help.push(`${errLocal.code}: ${errLocal.message}`);
|
||||
}
|
||||
if (errPackage.code !== 'MODULE_NOT_FOUND') {
|
||||
help.push(`${errPackage.code}: ${errPackage.message}`);
|
||||
}
|
||||
help.push('Possible solutions:');
|
||||
// Common error messages
|
||||
if (prebuiltPlatforms.includes(runtimePlatform)) {
|
||||
help.push('- Add an explicit dependency for the runtime platform:');
|
||||
help.push(` npm install --force @img/sharp-${runtimePlatform}`);
|
||||
} else {
|
||||
help.push(`- The ${runtimePlatform} platform requires manual installation of libvips >= ${minimumLibvipsVersion}`);
|
||||
}
|
||||
if (isLinux && /symbol not found/i.test(errPackage)) {
|
||||
try {
|
||||
const { engines } = require(`@img/sharp-libvips-${runtimePlatform}/package`);
|
||||
const libcFound = `${familySync()} ${versionSync()}`;
|
||||
const libcRequires = `${engines.musl ? 'musl' : 'glibc'} ${engines.musl || engines.glibc}`;
|
||||
help.push('- Update your OS:');
|
||||
help.push(` Found ${libcFound}`);
|
||||
help.push(` Requires ${libcRequires}`);
|
||||
} catch (errEngines) {}
|
||||
}
|
||||
if (isMacOs && /Incompatible library version/.test(errLocal.message)) {
|
||||
help.push('- Update Homebrew:');
|
||||
help.push(' brew update && brew upgrade vips');
|
||||
}
|
||||
if (errPackage.code === 'ERR_DLOPEN_DISABLED') {
|
||||
help.push('- Run Node.js without using the --no-addons flag');
|
||||
}
|
||||
if (process.versions.pnp) {
|
||||
help.push('- Use a supported yarn linker, either pnpm or node-modules:');
|
||||
help.push(' yarn config set nodeLinker node-modules');
|
||||
}
|
||||
// Link to installation docs
|
||||
if (isLinux && /Module did not self-register/.test(errLocal.message + errPackage.message)) {
|
||||
help.push('- Using worker threads on Linux? See https://sharp.pixelplumbing.com/install#worker-threads');
|
||||
} else if (isWindows && /The specified procedure could not be found/.test(errPackage.message)) {
|
||||
help.push('- Using the canvas package on Windows? See https://sharp.pixelplumbing.com/install#canvas-and-windows');
|
||||
} else {
|
||||
help.push('- Consult the installation documentation: https://sharp.pixelplumbing.com/install');
|
||||
}
|
||||
throw new Error(help.join('\n'));
|
||||
sharp = require(path);
|
||||
break;
|
||||
} catch (err) {
|
||||
/* istanbul ignore next */
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (sharp) {
|
||||
module.exports = sharp;
|
||||
} else {
|
||||
const [isLinux, isMacOs, isWindows] = ['linux', 'darwin', 'win32'].map(os => runtimePlatform.startsWith(os));
|
||||
|
||||
const help = [`Could not load the "sharp" module using the ${runtimePlatform} runtime`];
|
||||
errors.forEach(err => {
|
||||
if (err.code !== 'MODULE_NOT_FOUND') {
|
||||
help.push(`${err.code}: ${err.message}`);
|
||||
}
|
||||
});
|
||||
const messages = errors.map(err => err.message).join(' ');
|
||||
help.push('Possible solutions:');
|
||||
// Common error messages
|
||||
if (prebuiltPlatforms.includes(runtimePlatform)) {
|
||||
const [os, cpu] = runtimePlatform.split('-');
|
||||
help.push('- Add platform-specific dependencies:');
|
||||
help.push(` npm install --os=${os} --cpu=${cpu} sharp`);
|
||||
help.push(' or');
|
||||
help.push(` npm install --force @img/sharp-${runtimePlatform}`);
|
||||
} else {
|
||||
help.push(`- Manually install libvips >= ${minimumLibvipsVersion}`);
|
||||
help.push('- Add experimental WebAssembly-based dependencies:');
|
||||
help.push(' npm install --cpu=wasm32 sharp');
|
||||
}
|
||||
if (isLinux && /symbol not found/i.test(messages)) {
|
||||
try {
|
||||
const { engines } = require(`@img/sharp-libvips-${runtimePlatform}/package`);
|
||||
const libcFound = `${familySync()} ${versionSync()}`;
|
||||
const libcRequires = `${engines.musl ? 'musl' : 'glibc'} ${engines.musl || engines.glibc}`;
|
||||
help.push('- Update your OS:');
|
||||
help.push(` Found ${libcFound}`);
|
||||
help.push(` Requires ${libcRequires}`);
|
||||
} catch (errEngines) {}
|
||||
}
|
||||
if (isMacOs && /Incompatible library version/.test(messages)) {
|
||||
help.push('- Update Homebrew:');
|
||||
help.push(' brew update && brew upgrade vips');
|
||||
}
|
||||
if (errors.some(err => err.code === 'ERR_DLOPEN_DISABLED')) {
|
||||
help.push('- Run Node.js without using the --no-addons flag');
|
||||
}
|
||||
if (process.versions.pnp) {
|
||||
help.push('- Use a supported yarn linker, either pnpm or node-modules:');
|
||||
help.push(' yarn config set nodeLinker node-modules');
|
||||
}
|
||||
// Link to installation docs
|
||||
if (isWindows && /The specified procedure could not be found/.test(messages)) {
|
||||
help.push('- Using the canvas package on Windows? See https://sharp.pixelplumbing.com/install#canvas-and-windows');
|
||||
} else {
|
||||
help.push('- Consult the installation documentation: https://sharp.pixelplumbing.com/install');
|
||||
}
|
||||
throw new Error(help.join('\n'));
|
||||
}
|
||||
|
||||
@@ -59,11 +59,17 @@ let versions = {
|
||||
};
|
||||
/* istanbul ignore next */
|
||||
if (!libvipsVersion.isGlobal) {
|
||||
try {
|
||||
versions = require(`@img/sharp-${runtimePlatform}/versions`);
|
||||
} catch (_) {
|
||||
if (!libvipsVersion.isWasm) {
|
||||
try {
|
||||
versions = require(`@img/sharp-libvips-${runtimePlatform}/versions`);
|
||||
versions = require(`@img/sharp-${runtimePlatform}/versions`);
|
||||
} catch (_) {
|
||||
try {
|
||||
versions = require(`@img/sharp-libvips-${runtimePlatform}/versions`);
|
||||
} catch (_) {}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
versions = require('@img/sharp-wasm32/versions');
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-darwin-arm64",
|
||||
"version": "0.33.0-alpha.10",
|
||||
"version": "0.33.0-rc.1",
|
||||
"description": "Prebuilt sharp for use with macOS 64-bit ARM",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "0.0.3"
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-darwin-x64",
|
||||
"version": "0.33.0-alpha.10",
|
||||
"version": "0.33.0-rc.1",
|
||||
"description": "Prebuilt sharp for use with macOS x64",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "0.0.3"
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -38,7 +38,8 @@ limitations under the License.
|
||||
`;
|
||||
|
||||
workspaces.map(async platform => {
|
||||
const url = `https://github.com/lovell/sharp/releases/download/v${version}/sharp-v${version}-napi-v9-${platform}.tar.gz`;
|
||||
const prebuildPlatform = platform === 'wasm32' ? 'emscripten-wasm32' : platform;
|
||||
const url = `https://github.com/lovell/sharp/releases/download/v${version}/sharp-v${version}-napi-v9-${prebuildPlatform}.tar.gz`;
|
||||
const dir = path.join(__dirname, platform);
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
@@ -47,7 +48,7 @@ workspaces.map(async platform => {
|
||||
}
|
||||
// Extract prebuild tarball
|
||||
const lib = path.join(dir, 'lib');
|
||||
await rm(lib, { recursive: true });
|
||||
await rm(lib, { force: true, recursive: true });
|
||||
await pipeline(
|
||||
Readable.fromWeb(response.body),
|
||||
createGunzip(),
|
||||
@@ -58,9 +59,10 @@ workspaces.map(async platform => {
|
||||
await writeFile(path.join(dir, 'README.md'), `# \`${name}\`\n\n${description}.\n${licensing}`);
|
||||
// Copy Apache-2.0 LICENSE
|
||||
await copyFile(path.join(__dirname, '..', 'LICENSE'), path.join(dir, 'LICENSE'));
|
||||
// Copy Windows-specific files
|
||||
if (platform.startsWith('win32-')) {
|
||||
const sharpLibvipsDir = path.join(require(`@img/sharp-libvips-${platform}/lib`), '..');
|
||||
// Copy files for packages without an explicit sharp-libvips dependency (Windows, wasm)
|
||||
if (platform.startsWith('win') || platform.startsWith('wasm')) {
|
||||
const libvipsPlatform = platform === 'wasm32' ? 'dev-wasm32' : platform;
|
||||
const sharpLibvipsDir = path.join(require(`@img/sharp-libvips-${libvipsPlatform}/lib`), '..');
|
||||
// Copy versions.json
|
||||
await copyFile(path.join(sharpLibvipsDir, 'versions.json'), path.join(dir, 'versions.json'));
|
||||
// Append third party licensing to README
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-linux-arm",
|
||||
"version": "0.33.0-alpha.10",
|
||||
"version": "0.33.0-rc.1",
|
||||
"description": "Prebuilt sharp for use with Linux (glibc) ARM (32-bit)",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "0.0.3"
|
||||
"@img/sharp-libvips-linux-arm": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-linux-arm64",
|
||||
"version": "0.33.0-alpha.10",
|
||||
"version": "0.33.0-rc.1",
|
||||
"description": "Prebuilt sharp for use with Linux (glibc) 64-bit ARM",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "0.0.3"
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-linux-s390x",
|
||||
"version": "0.33.0-alpha.10",
|
||||
"version": "0.33.0-rc.1",
|
||||
"description": "Prebuilt sharp for use with Linux (glibc) s390x",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "0.0.3"
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-linux-x64",
|
||||
"version": "0.33.0-alpha.10",
|
||||
"version": "0.33.0-rc.1",
|
||||
"description": "Prebuilt sharp for use with Linux (glibc) x64",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "0.0.3"
|
||||
"@img/sharp-libvips-linux-x64": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-linuxmusl-arm64",
|
||||
"version": "0.33.0-alpha.10",
|
||||
"version": "0.33.0-rc.1",
|
||||
"description": "Prebuilt sharp for use with Linux (musl) 64-bit ARM",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "0.0.3"
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-linuxmusl-x64",
|
||||
"version": "0.33.0-alpha.10",
|
||||
"version": "0.33.0-rc.1",
|
||||
"description": "Prebuilt sharp for use with Linux (musl) x64",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "0.0.3"
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp",
|
||||
"version": "0.33.0-alpha.10",
|
||||
"version": "0.33.0-rc.1",
|
||||
"private": "true",
|
||||
"workspaces": [
|
||||
"darwin-arm64",
|
||||
@@ -11,6 +11,7 @@
|
||||
"linux-x64",
|
||||
"linuxmusl-arm64",
|
||||
"linuxmusl-x64",
|
||||
"wasm32",
|
||||
"win32-ia32",
|
||||
"win32-x64"
|
||||
]
|
||||
|
||||
42
npm/wasm32/package.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"name": "@img/sharp-wasm32",
|
||||
"version": "0.33.0-rc.1",
|
||||
"description": "Prebuilt sharp for use with wasm32",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/lovell/sharp.git",
|
||||
"directory": "npm/wasm32"
|
||||
},
|
||||
"license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/libvips"
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"files": [
|
||||
"lib",
|
||||
"versions.json"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"type": "commonjs",
|
||||
"exports": {
|
||||
"./sharp.node": "./lib/sharp-wasm32.node.js",
|
||||
"./package": "./package.json",
|
||||
"./versions": "./versions.json"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
|
||||
"npm": ">=9.6.5",
|
||||
"yarn": ">=3.2.0",
|
||||
"pnpm": ">=7.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^0.44.0"
|
||||
},
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-win32-ia32",
|
||||
"version": "0.33.0-alpha.10",
|
||||
"version": "0.33.0-rc.1",
|
||||
"description": "Prebuilt sharp for use with Windows x86 (32-bit)",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-win32-x64",
|
||||
"version": "0.33.0-alpha.10",
|
||||
"version": "0.33.0-rc.1",
|
||||
"description": "Prebuilt sharp for use with Windows x64",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
|
||||
58
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "sharp",
|
||||
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
|
||||
"version": "0.33.0-alpha.10",
|
||||
"version": "0.33.0-rc.1",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://github.com/lovell/sharp",
|
||||
"contributors": [
|
||||
@@ -87,7 +87,8 @@
|
||||
"Brahim Ait elhaj <brahima@gmail.com>",
|
||||
"Mart Jansink <m.jansink@gmail.com>",
|
||||
"Lachlan Newman <lachnewman007@gmail.com>",
|
||||
"Dennis Beatty <dennis@dcbeatty.com>"
|
||||
"Dennis Beatty <dennis@dcbeatty.com>",
|
||||
"Ingvar Stepanyan <me@rreverser.com>"
|
||||
],
|
||||
"scripts": {
|
||||
"install": "node install/check",
|
||||
@@ -140,32 +141,36 @@
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.33.0-alpha.10",
|
||||
"@img/sharp-darwin-x64": "0.33.0-alpha.10",
|
||||
"@img/sharp-libvips-darwin-arm64": "0.0.3",
|
||||
"@img/sharp-libvips-darwin-x64": "0.0.3",
|
||||
"@img/sharp-libvips-linux-arm": "0.0.3",
|
||||
"@img/sharp-libvips-linux-arm64": "0.0.3",
|
||||
"@img/sharp-libvips-linux-s390x": "0.0.3",
|
||||
"@img/sharp-libvips-linux-x64": "0.0.3",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "0.0.3",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "0.0.3",
|
||||
"@img/sharp-linux-arm": "0.33.0-alpha.10",
|
||||
"@img/sharp-linux-arm64": "0.33.0-alpha.10",
|
||||
"@img/sharp-linux-s390x": "0.33.0-alpha.10",
|
||||
"@img/sharp-linux-x64": "0.33.0-alpha.10",
|
||||
"@img/sharp-linuxmusl-arm64": "0.33.0-alpha.10",
|
||||
"@img/sharp-linuxmusl-x64": "0.33.0-alpha.10",
|
||||
"@img/sharp-win32-ia32": "0.33.0-alpha.10",
|
||||
"@img/sharp-win32-x64": "0.33.0-alpha.10"
|
||||
"@img/sharp-darwin-arm64": "0.33.0-rc.1",
|
||||
"@img/sharp-darwin-x64": "0.33.0-rc.1",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.0",
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.0",
|
||||
"@img/sharp-libvips-linux-arm": "1.0.0",
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.0",
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.0",
|
||||
"@img/sharp-libvips-linux-x64": "1.0.0",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.0",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.0",
|
||||
"@img/sharp-linux-arm": "0.33.0-rc.1",
|
||||
"@img/sharp-linux-arm64": "0.33.0-rc.1",
|
||||
"@img/sharp-linux-s390x": "0.33.0-rc.1",
|
||||
"@img/sharp-linux-x64": "0.33.0-rc.1",
|
||||
"@img/sharp-linuxmusl-arm64": "0.33.0-rc.1",
|
||||
"@img/sharp-linuxmusl-x64": "0.33.0-rc.1",
|
||||
"@img/sharp-wasm32": "0.33.0-rc.1",
|
||||
"@img/sharp-win32-ia32": "0.33.0-rc.1",
|
||||
"@img/sharp-win32-x64": "0.33.0-rc.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@img/sharp-libvips-dev": "0.0.3",
|
||||
"@img/sharp-libvips-win32-ia32": "0.0.3",
|
||||
"@img/sharp-libvips-win32-x64": "0.0.3",
|
||||
"@emnapi/runtime": "^0.44.0",
|
||||
"@img/sharp-libvips-dev": "1.0.0",
|
||||
"@img/sharp-libvips-dev-wasm32": "1.0.0",
|
||||
"@img/sharp-libvips-win32-ia32": "1.0.0",
|
||||
"@img/sharp-libvips-win32-x64": "1.0.0",
|
||||
"@types/node": "*",
|
||||
"async": "^3.2.4",
|
||||
"async": "^3.2.5",
|
||||
"cc": "^3.0.1",
|
||||
"emnapi": "^0.44.0",
|
||||
"exif-reader": "^2.0.0",
|
||||
"extract-zip": "^2.0.1",
|
||||
"icc": "^3.0.0",
|
||||
@@ -203,6 +208,11 @@
|
||||
"build/include"
|
||||
]
|
||||
},
|
||||
"nyc": {
|
||||
"include": [
|
||||
"lib"
|
||||
]
|
||||
},
|
||||
"tsd": {
|
||||
"directory": "test/types/"
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
'OTHER_LDFLAGS': [
|
||||
# Ensure runtime linking is relative to sharp.node
|
||||
'-Wl,-rpath,\'@loader_path/../../sharp-libvips-<(platform_and_arch)/lib\'',
|
||||
'-Wl,-rpath,\'@loader_path/../../../<(sharp_libvips_version)/sharp-libvips-<(platform_and_arch)/lib\'',
|
||||
'-Wl,-rpath,\'@loader_path/../../../sharp-libvips-<(platform_and_arch)/<(sharp_libvips_version)/lib\'',
|
||||
'-Wl,-rpath,\'@loader_path/../../node_modules/@img/sharp-libvips-<(platform_and_arch)/lib\'',
|
||||
'-Wl,-rpath,\'@loader_path/../../../node_modules/@img/sharp-libvips-<(platform_and_arch)/lib\''
|
||||
]
|
||||
@@ -170,15 +170,35 @@
|
||||
'-l:libvips-cpp.so.42'
|
||||
],
|
||||
'ldflags': [
|
||||
# Ensure runtime linking is relative to sharp.node
|
||||
'-Wl,-s',
|
||||
'-Wl,--disable-new-dtags',
|
||||
'-Wl,-z,nodelete',
|
||||
'-Wl,-rpath=\'$$ORIGIN/../../sharp-libvips-<(platform_and_arch)/lib\'',
|
||||
'-Wl,-rpath=\'$$ORIGIN/../../../<(sharp_libvips_version)/sharp-libvips-<(platform_and_arch)/lib\'',
|
||||
'-Wl,-rpath=\'$$ORIGIN/../../../sharp-libvips-<(platform_and_arch)/<(sharp_libvips_version)/lib\'',
|
||||
'-Wl,-rpath=\'$$ORIGIN/../../node_modules/@img/sharp-libvips-<(platform_and_arch)/lib\'',
|
||||
'-Wl,-rpath=\'$$ORIGIN/../../../node_modules/@img/sharp-libvips-<(platform_and_arch)/lib\''
|
||||
]
|
||||
}
|
||||
}],
|
||||
['OS == "emscripten"', {
|
||||
'product_extension': 'node.js',
|
||||
'link_settings': {
|
||||
'ldflags': [
|
||||
'-fexceptions',
|
||||
'--pre-js=<!(node -p "require.resolve(\'./emscripten/pre.js\')")',
|
||||
'-Oz',
|
||||
'-sALLOW_MEMORY_GROWTH',
|
||||
'-sENVIRONMENT=node',
|
||||
'-sEXPORTED_FUNCTIONS=["_vips_shutdown", "_uv_library_shutdown"]',
|
||||
'-sNODERAWFS',
|
||||
'-sTEXTDECODER=0',
|
||||
'-sWASM_ASYNC_COMPILATION=0',
|
||||
'-sWASM_BIGINT'
|
||||
],
|
||||
'libraries': [
|
||||
'<!@(PKG_CONFIG_PATH="<!(node -p "require(\'@img/sharp-libvips-dev-wasm32/lib\')")/pkgconfig" pkg-config --static --libs vips-cpp)'
|
||||
],
|
||||
}
|
||||
}]
|
||||
]
|
||||
}]
|
||||
@@ -203,11 +223,6 @@
|
||||
'configurations': {
|
||||
'Release': {
|
||||
'conditions': [
|
||||
['OS == "linux"', {
|
||||
'cflags_cc': [
|
||||
'-Wno-cast-function-type'
|
||||
]
|
||||
}],
|
||||
['target_arch == "arm"', {
|
||||
'cflags_cc': [
|
||||
'-Wno-psabi'
|
||||
|
||||
@@ -531,7 +531,33 @@ namespace sharp {
|
||||
Does this image have an embedded profile?
|
||||
*/
|
||||
bool HasProfile(VImage image) {
|
||||
return (image.get_typeof(VIPS_META_ICC_NAME) != 0) ? TRUE : FALSE;
|
||||
return image.get_typeof(VIPS_META_ICC_NAME) == VIPS_TYPE_BLOB;
|
||||
}
|
||||
|
||||
/*
|
||||
Get copy of embedded profile.
|
||||
*/
|
||||
std::pair<char*, size_t> GetProfile(VImage image) {
|
||||
std::pair<char*, size_t> icc(nullptr, 0);
|
||||
if (HasProfile(image)) {
|
||||
size_t length;
|
||||
const void *data = image.get_blob(VIPS_META_ICC_NAME, &length);
|
||||
icc.first = static_cast<char*>(g_malloc(length));
|
||||
icc.second = length;
|
||||
memcpy(icc.first, data, length);
|
||||
}
|
||||
return icc;
|
||||
}
|
||||
|
||||
/*
|
||||
Set embedded profile.
|
||||
*/
|
||||
VImage SetProfile(VImage image, std::pair<char*, size_t> icc) {
|
||||
if (icc.first != nullptr) {
|
||||
image = image.copy();
|
||||
image.set(VIPS_META_ICC_NAME, reinterpret_cast<VipsCallbackFn>(vips_area_free_cb), icc.first, icc.second);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -542,6 +568,27 @@ namespace sharp {
|
||||
return image.has_alpha();
|
||||
}
|
||||
|
||||
static void* RemoveExifCallback(VipsImage *image, char const *field, GValue *value, void *data) {
|
||||
std::vector<std::string> *fieldNames = static_cast<std::vector<std::string> *>(data);
|
||||
std::string fieldName(field);
|
||||
if (fieldName.substr(0, 8) == ("exif-ifd")) {
|
||||
fieldNames->push_back(fieldName);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove all EXIF-related image fields.
|
||||
*/
|
||||
VImage RemoveExif(VImage image) {
|
||||
std::vector<std::string> fieldNames;
|
||||
vips_image_map(image.get_image(), static_cast<VipsImageMapFn>(RemoveExifCallback), &fieldNames);
|
||||
for (const auto& f : fieldNames) {
|
||||
image.remove(f.data());
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
/*
|
||||
Get EXIF Orientation of image, if any.
|
||||
*/
|
||||
|
||||
15
src/common.h
@@ -222,12 +222,27 @@ namespace sharp {
|
||||
*/
|
||||
bool HasProfile(VImage image);
|
||||
|
||||
/*
|
||||
Get copy of embedded profile.
|
||||
*/
|
||||
std::pair<char*, size_t> GetProfile(VImage image);
|
||||
|
||||
/*
|
||||
Set embedded profile.
|
||||
*/
|
||||
VImage SetProfile(VImage image, std::pair<char*, size_t> icc);
|
||||
|
||||
/*
|
||||
Does this image have an alpha channel?
|
||||
Uses colour space interpretation with number of channels to guess this.
|
||||
*/
|
||||
bool HasAlpha(VImage image);
|
||||
|
||||
/*
|
||||
Remove all EXIF-related image fields.
|
||||
*/
|
||||
VImage RemoveExif(VImage image);
|
||||
|
||||
/*
|
||||
Get EXIF Orientation of image, if any.
|
||||
*/
|
||||
|
||||
40
src/emscripten/common.gypi
Normal file
@@ -0,0 +1,40 @@
|
||||
# Copyright 2013 Lovell Fuller and others.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
{
|
||||
'variables': {
|
||||
'OS': 'emscripten'
|
||||
},
|
||||
'target_defaults': {
|
||||
'default_configuration': 'Release',
|
||||
'type': 'executable',
|
||||
'cflags': [
|
||||
'-pthread',
|
||||
'-sDEFAULT_TO_CXX=0'
|
||||
],
|
||||
'cflags_cc': [
|
||||
'-pthread'
|
||||
],
|
||||
'ldflags': [
|
||||
'--js-library=<!(node -p "require(\'emnapi\').js_library")',
|
||||
'-sAUTO_JS_LIBRARIES=0',
|
||||
'-sAUTO_NATIVE_LIBRARIES=0',
|
||||
'-sNODEJS_CATCH_EXIT=0',
|
||||
'-sNODEJS_CATCH_REJECTION=0'
|
||||
],
|
||||
'defines': [
|
||||
'__STDC_FORMAT_MACROS',
|
||||
'BUILDING_NODE_EXTENSION',
|
||||
'EMNAPI_WORKER_POOL_SIZE=1'
|
||||
],
|
||||
'include_dirs': [
|
||||
'<!(node -p "require(\'emnapi\').include")'
|
||||
],
|
||||
'sources': [
|
||||
'<!@(node -p "require(\'emnapi\').sources.map(x => JSON.stringify(path.relative(process.cwd(), x))).join(\' \')")'
|
||||
],
|
||||
'configurations': {
|
||||
'Release': {}
|
||||
}
|
||||
}
|
||||
}
|
||||
19
src/emscripten/pre.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2013 Lovell Fuller and others.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
/* global Module, ENV, _vips_shutdown, _uv_library_shutdown */
|
||||
|
||||
Module.preRun = () => {
|
||||
ENV.VIPS_CONCURRENCY = Number(process.env.VIPS_CONCURRENCY) || 1;
|
||||
};
|
||||
|
||||
Module.onRuntimeInitialized = () => {
|
||||
module.exports = Module.emnapiInit({
|
||||
context: require('@emnapi/runtime').getDefaultContext()
|
||||
});
|
||||
|
||||
process.once('exit', () => {
|
||||
_vips_shutdown();
|
||||
_uv_library_shutdown();
|
||||
});
|
||||
};
|
||||
@@ -16,30 +16,44 @@ using vips::VError;
|
||||
|
||||
namespace sharp {
|
||||
/*
|
||||
* Tint an image using the specified chroma, preserving the original image luminance
|
||||
* Tint an image using the provided RGB.
|
||||
*/
|
||||
VImage Tint(VImage image, double const a, double const b) {
|
||||
// Get original colourspace
|
||||
VImage Tint(VImage image, std::vector<double> const tint) {
|
||||
std::vector<double> const tintLab = (VImage::black(1, 1) + tint)
|
||||
.colourspace(VIPS_INTERPRETATION_LAB, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB))
|
||||
.getpoint(0, 0);
|
||||
// LAB identity function
|
||||
VImage identityLab = VImage::identity(VImage::option()->set("bands", 3))
|
||||
.colourspace(VIPS_INTERPRETATION_LAB, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
|
||||
// Scale luminance range, 0.0 to 1.0
|
||||
VImage l = identityLab[0] / 100;
|
||||
// Weighting functions
|
||||
VImage weightL = 1.0 - 4.0 * ((l - 0.5) * (l - 0.5));
|
||||
VImage weightAB = (weightL * tintLab).extract_band(1, VImage::option()->set("n", 2));
|
||||
identityLab = identityLab[0].bandjoin(weightAB);
|
||||
// Convert lookup table to sRGB
|
||||
VImage lut = identityLab.colourspace(VIPS_INTERPRETATION_sRGB,
|
||||
VImage::option()->set("source_space", VIPS_INTERPRETATION_LAB));
|
||||
// Original colourspace
|
||||
VipsInterpretation typeBeforeTint = image.interpretation();
|
||||
if (typeBeforeTint == VIPS_INTERPRETATION_RGB) {
|
||||
typeBeforeTint = VIPS_INTERPRETATION_sRGB;
|
||||
}
|
||||
// Extract luminance
|
||||
VImage luminance = image.colourspace(VIPS_INTERPRETATION_LAB)[0];
|
||||
// Create the tinted version by combining the L from the original and the chroma from the tint
|
||||
std::vector<double> chroma {a, b};
|
||||
VImage tinted = luminance
|
||||
.bandjoin(chroma)
|
||||
.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_LAB))
|
||||
.colourspace(typeBeforeTint);
|
||||
// Attach original alpha channel, if any
|
||||
// Apply lookup table
|
||||
if (HasAlpha(image)) {
|
||||
// Extract original alpha channel
|
||||
VImage alpha = image[image.bands() - 1];
|
||||
// Join alpha channel to normalised image
|
||||
tinted = tinted.bandjoin(alpha);
|
||||
image = RemoveAlpha(image)
|
||||
.colourspace(VIPS_INTERPRETATION_B_W)
|
||||
.maplut(lut)
|
||||
.colourspace(typeBeforeTint)
|
||||
.bandjoin(alpha);
|
||||
} else {
|
||||
image = image
|
||||
.colourspace(VIPS_INTERPRETATION_B_W)
|
||||
.maplut(lut)
|
||||
.colourspace(typeBeforeTint);
|
||||
}
|
||||
return tinted;
|
||||
return image;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -15,9 +15,9 @@ using vips::VImage;
|
||||
namespace sharp {
|
||||
|
||||
/*
|
||||
* Tint an image using the specified chroma, preserving the original image luminance
|
||||
* Tint an image using the provided RGB.
|
||||
*/
|
||||
VImage Tint(VImage image, double const a, double const b);
|
||||
VImage Tint(VImage image, std::vector<double> const tint);
|
||||
|
||||
/*
|
||||
* Stretch luminance to cover full dynamic range.
|
||||
|
||||
@@ -315,6 +315,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
}
|
||||
|
||||
// Ensure we're using a device-independent colour space
|
||||
std::pair<char*, size_t> inputProfile(nullptr, 0);
|
||||
if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->withIccProfile.empty()) {
|
||||
// Cache input profile for use with output
|
||||
inputProfile = sharp::GetProfile(image);
|
||||
}
|
||||
char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb";
|
||||
if (
|
||||
sharp::HasProfile(image) &&
|
||||
@@ -736,8 +741,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
}
|
||||
|
||||
// Tint the image
|
||||
if (baton->tintA < 128.0 || baton->tintB < 128.0) {
|
||||
image = sharp::Tint(image, baton->tintA, baton->tintB);
|
||||
if (baton->tint[0] >= 0.0) {
|
||||
image = sharp::Tint(image, baton->tint);
|
||||
}
|
||||
|
||||
// Remove alpha channel, if any
|
||||
@@ -758,7 +763,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
|
||||
image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
|
||||
// Transform colours from embedded profile to output profile
|
||||
if (baton->withMetadata && sharp::HasProfile(image) && baton->withMetadataIcc.empty()) {
|
||||
if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) &&
|
||||
baton->withIccProfile.empty() && sharp::HasProfile(image)) {
|
||||
image = image.icc_transform("srgb", VImage::option()
|
||||
->set("embedded", TRUE)
|
||||
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
|
||||
@@ -787,27 +793,30 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
}
|
||||
|
||||
// Apply output ICC profile
|
||||
if (baton->withMetadata) {
|
||||
image = image.icc_transform(
|
||||
baton->withMetadataIcc.empty() ? "srgb" : const_cast<char*>(baton->withMetadataIcc.data()),
|
||||
VImage::option()
|
||||
->set("input_profile", processingProfile)
|
||||
->set("embedded", TRUE)
|
||||
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
|
||||
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
||||
if (!baton->withIccProfile.empty()) {
|
||||
image = image.icc_transform(const_cast<char*>(baton->withIccProfile.data()), VImage::option()
|
||||
->set("input_profile", processingProfile)
|
||||
->set("embedded", TRUE)
|
||||
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
|
||||
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
||||
} else if (baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) {
|
||||
image = sharp::SetProfile(image, inputProfile);
|
||||
}
|
||||
// Override EXIF Orientation tag
|
||||
if (baton->withMetadata && baton->withMetadataOrientation != -1) {
|
||||
if (baton->withMetadataOrientation != -1) {
|
||||
image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
|
||||
}
|
||||
// Override pixel density
|
||||
if (baton->withMetadataDensity > 0) {
|
||||
image = sharp::SetDensity(image, baton->withMetadataDensity);
|
||||
}
|
||||
// Metadata key/value pairs, e.g. EXIF
|
||||
if (!baton->withMetadataStrs.empty()) {
|
||||
// EXIF key/value pairs
|
||||
if (baton->keepMetadata & VIPS_FOREIGN_KEEP_EXIF) {
|
||||
image = image.copy();
|
||||
for (const auto& s : baton->withMetadataStrs) {
|
||||
if (!baton->withExifMerge) {
|
||||
image = sharp::RemoveExif(image);
|
||||
}
|
||||
for (const auto& s : baton->withExif) {
|
||||
image.set(s.first.data(), s.second.data());
|
||||
}
|
||||
}
|
||||
@@ -828,7 +837,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write JPEG to buffer
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->jpegQuality)
|
||||
->set("interlace", baton->jpegProgressive)
|
||||
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
||||
@@ -870,7 +879,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write PNG to buffer
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("interlace", baton->pngProgressive)
|
||||
->set("compression", baton->pngCompressionLevel)
|
||||
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
||||
@@ -889,7 +898,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write WEBP to buffer
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.webpsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->webpQuality)
|
||||
->set("lossless", baton->webpLossless)
|
||||
->set("near_lossless", baton->webpNearLossless)
|
||||
@@ -909,7 +918,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write GIF to buffer
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.gifsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("bitdepth", baton->gifBitdepth)
|
||||
->set("effort", baton->gifEffort)
|
||||
->set("reuse", baton->gifReuse)
|
||||
@@ -934,7 +943,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
image = image.cast(VIPS_FORMAT_FLOAT);
|
||||
}
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.tiffsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->tiffQuality)
|
||||
->set("bitdepth", baton->tiffBitdepth)
|
||||
->set("compression", baton->tiffCompression)
|
||||
@@ -958,7 +967,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
|
||||
image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->heifQuality)
|
||||
->set("compression", baton->heifCompression)
|
||||
->set("effort", baton->heifEffort)
|
||||
@@ -990,7 +999,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write JXL to buffer
|
||||
image = sharp::RemoveAnimationProperties(image);
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.jxlsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("distance", baton->jxlDistance)
|
||||
->set("tier", baton->jxlDecodingTier)
|
||||
->set("effort", baton->jxlEffort)
|
||||
@@ -1051,7 +1060,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write JPEG to file
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
||||
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->jpegQuality)
|
||||
->set("interlace", baton->jpegProgressive)
|
||||
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
||||
@@ -1081,7 +1090,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write PNG to file
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
||||
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("interlace", baton->pngProgressive)
|
||||
->set("compression", baton->pngCompressionLevel)
|
||||
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
||||
@@ -1096,7 +1105,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write WEBP to file
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
|
||||
image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->webpQuality)
|
||||
->set("lossless", baton->webpLossless)
|
||||
->set("near_lossless", baton->webpNearLossless)
|
||||
@@ -1112,7 +1121,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write GIF to file
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
||||
image.gifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("bitdepth", baton->gifBitdepth)
|
||||
->set("effort", baton->gifEffort)
|
||||
->set("reuse", baton->gifReuse)
|
||||
@@ -1131,7 +1140,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
image = image.cast(VIPS_FORMAT_FLOAT);
|
||||
}
|
||||
image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->tiffQuality)
|
||||
->set("bitdepth", baton->tiffBitdepth)
|
||||
->set("compression", baton->tiffCompression)
|
||||
@@ -1151,7 +1160,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
|
||||
image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
|
||||
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->heifQuality)
|
||||
->set("compression", baton->heifCompression)
|
||||
->set("effort", baton->heifEffort)
|
||||
@@ -1165,7 +1174,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write JXL to file
|
||||
image = sharp::RemoveAnimationProperties(image);
|
||||
image.jxlsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("distance", baton->jxlDistance)
|
||||
->set("tier", baton->jxlDecodingTier)
|
||||
->set("effort", baton->jxlEffort)
|
||||
@@ -1187,7 +1196,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
(willMatchInput && inputImageType == sharp::ImageType::VIPS)) {
|
||||
// Write V to file
|
||||
image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata));
|
||||
->set("keep", baton->keepMetadata));
|
||||
baton->formatOut = "v";
|
||||
} else {
|
||||
// Unsupported output format
|
||||
@@ -1401,7 +1410,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
suffix = AssembleSuffixString(extname, options);
|
||||
}
|
||||
vips::VOption *options = VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("tile_size", baton->tileSize)
|
||||
->set("overlap", baton->tileOverlap)
|
||||
->set("container", baton->tileContainer)
|
||||
@@ -1527,8 +1536,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
baton->normalise = sharp::AttrAsBool(options, "normalise");
|
||||
baton->normaliseLower = sharp::AttrAsUint32(options, "normaliseLower");
|
||||
baton->normaliseUpper = sharp::AttrAsUint32(options, "normaliseUpper");
|
||||
baton->tintA = sharp::AttrAsDouble(options, "tintA");
|
||||
baton->tintB = sharp::AttrAsDouble(options, "tintB");
|
||||
baton->tint = sharp::AttrAsVectorOfDouble(options, "tint");
|
||||
baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth");
|
||||
baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight");
|
||||
baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope");
|
||||
@@ -1594,18 +1602,19 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
// Output
|
||||
baton->formatOut = sharp::AttrAsStr(options, "formatOut");
|
||||
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
|
||||
baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
|
||||
baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata");
|
||||
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
|
||||
baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
|
||||
baton->withMetadataIcc = sharp::AttrAsStr(options, "withMetadataIcc");
|
||||
Napi::Object mdStrs = options.Get("withMetadataStrs").As<Napi::Object>();
|
||||
Napi::Array mdStrKeys = mdStrs.GetPropertyNames();
|
||||
for (unsigned int i = 0; i < mdStrKeys.Length(); i++) {
|
||||
std::string k = sharp::AttrAsStr(mdStrKeys, i);
|
||||
if (mdStrs.HasOwnProperty(k)) {
|
||||
baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
|
||||
baton->withIccProfile = sharp::AttrAsStr(options, "withIccProfile");
|
||||
Napi::Object withExif = options.Get("withExif").As<Napi::Object>();
|
||||
Napi::Array withExifKeys = withExif.GetPropertyNames();
|
||||
for (unsigned int i = 0; i < withExifKeys.Length(); i++) {
|
||||
std::string k = sharp::AttrAsStr(withExifKeys, i);
|
||||
if (withExif.HasOwnProperty(k)) {
|
||||
baton->withExif.insert(std::make_pair(k, sharp::AttrAsStr(withExif, k)));
|
||||
}
|
||||
}
|
||||
baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
|
||||
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
|
||||
// Format-specific
|
||||
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
|
||||
|
||||
@@ -69,8 +69,7 @@ struct PipelineBaton {
|
||||
bool premultiplied;
|
||||
bool tileCentre;
|
||||
bool fastShrinkOnLoad;
|
||||
double tintA;
|
||||
double tintB;
|
||||
std::vector<double> tint;
|
||||
bool flatten;
|
||||
std::vector<double> flattenBackground;
|
||||
bool unflatten;
|
||||
@@ -188,11 +187,12 @@ struct PipelineBaton {
|
||||
bool jxlLossless;
|
||||
VipsBandFormat rawDepth;
|
||||
std::string err;
|
||||
bool withMetadata;
|
||||
int keepMetadata;
|
||||
int withMetadataOrientation;
|
||||
double withMetadataDensity;
|
||||
std::string withMetadataIcc;
|
||||
std::unordered_map<std::string, std::string> withMetadataStrs;
|
||||
std::string withIccProfile;
|
||||
std::unordered_map<std::string, std::string> withExif;
|
||||
bool withExifMerge;
|
||||
int timeoutSeconds;
|
||||
std::unique_ptr<double[]> convKernel;
|
||||
int convKernelWidth;
|
||||
@@ -239,8 +239,7 @@ struct PipelineBaton {
|
||||
attentionX(0),
|
||||
attentionY(0),
|
||||
premultiplied(false),
|
||||
tintA(128.0),
|
||||
tintB(128.0),
|
||||
tint{ -1.0, 0.0, 0.0, 0.0 },
|
||||
flatten(false),
|
||||
flattenBackground{ 0.0, 0.0, 0.0 },
|
||||
unflatten(false),
|
||||
@@ -355,9 +354,10 @@ struct PipelineBaton {
|
||||
jxlEffort(7),
|
||||
jxlLossless(false),
|
||||
rawDepth(VIPS_FORMAT_UCHAR),
|
||||
withMetadata(false),
|
||||
keepMetadata(0),
|
||||
withMetadataOrientation(-1),
|
||||
withMetadataDensity(0.0),
|
||||
withExifMerge(true),
|
||||
timeoutSeconds(0),
|
||||
convKernelWidth(0),
|
||||
convKernelHeight(0),
|
||||
|
||||
@@ -102,6 +102,11 @@ Napi::Value libvipsVersion(const Napi::CallbackInfo& info) {
|
||||
version.Set("isGlobal", Napi::Boolean::New(env, true));
|
||||
#else
|
||||
version.Set("isGlobal", Napi::Boolean::New(env, false));
|
||||
#endif
|
||||
#ifdef __EMSCRIPTEN__
|
||||
version.Set("isWasm", Napi::Boolean::New(env, true));
|
||||
#else
|
||||
version.Set("isWasm", Napi::Boolean::New(env, false));
|
||||
#endif
|
||||
return version;
|
||||
}
|
||||
|
||||
BIN
test/fixtures/expected/tint-alpha.png
vendored
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 135 KiB |
BIN
test/fixtures/expected/tint-blue.jpg
vendored
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
BIN
test/fixtures/expected/tint-cmyk.jpg
vendored
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 30 KiB |
BIN
test/fixtures/expected/tint-green.jpg
vendored
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
BIN
test/fixtures/expected/tint-red.jpg
vendored
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
BIN
test/fixtures/expected/tint-sepia.jpg
vendored
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
@@ -659,6 +659,18 @@ sharp('input.tiff').webp({ preset: 'drawing' }).toFile('out.webp');
|
||||
sharp('input.tiff').webp({ preset: 'text' }).toFile('out.webp');
|
||||
sharp('input.tiff').webp({ preset: 'default' }).toFile('out.webp');
|
||||
|
||||
// Allow a boolean or an object for metadata options.
|
||||
// https://github.com/lovell/sharp/issues/3822
|
||||
sharp(input).withMetadata().withMetadata({}).withMetadata(false);
|
||||
sharp(input)
|
||||
.keepExif()
|
||||
.withExif({
|
||||
IFD0: {
|
||||
k1: 'v1'
|
||||
}
|
||||
})
|
||||
.withExifMerge({
|
||||
IFD1: {
|
||||
k2: 'v2'
|
||||
}
|
||||
})
|
||||
.keepIccProfile()
|
||||
.withIccProfile('filename')
|
||||
.withIccProfile('filename', { attach: false });
|
||||
|
||||
@@ -69,17 +69,25 @@ describe('libvips binaries', function () {
|
||||
});
|
||||
|
||||
describe('Build time platform detection', () => {
|
||||
it('Can override platform with npm_config_platform and npm_config_libc', () => {
|
||||
it('Can override platform with npm_config_platform and npm_config_libc', function () {
|
||||
process.env.npm_config_platform = 'testplatform';
|
||||
process.env.npm_config_libc = 'testlibc';
|
||||
const [platform] = libvips.buildPlatformArch().split('-');
|
||||
const platformArch = libvips.buildPlatformArch();
|
||||
if (platformArch === 'wasm32') {
|
||||
return this.skip();
|
||||
}
|
||||
const [platform] = platformArch.split('-');
|
||||
assert.strictEqual(platform, 'testplatformtestlibc');
|
||||
delete process.env.npm_config_platform;
|
||||
delete process.env.npm_config_libc;
|
||||
});
|
||||
it('Can override arch with npm_config_arch', () => {
|
||||
it('Can override arch with npm_config_arch', function () {
|
||||
process.env.npm_config_arch = 'test';
|
||||
const [, arch] = libvips.buildPlatformArch().split('-');
|
||||
const platformArch = libvips.buildPlatformArch();
|
||||
if (platformArch === 'wasm32') {
|
||||
return this.skip();
|
||||
}
|
||||
const [, arch] = platformArch.split('-');
|
||||
assert.strictEqual(arch, 'test');
|
||||
delete process.env.npm_config_arch;
|
||||
});
|
||||
|
||||
@@ -11,6 +11,8 @@ const icc = require('icc');
|
||||
const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
const create = { width: 1, height: 1, channels: 3, background: 'red' };
|
||||
|
||||
describe('Image metadata', function () {
|
||||
it('JPEG', function (done) {
|
||||
sharp(fixtures.inputJpg).metadata(function (err, metadata) {
|
||||
@@ -552,11 +554,55 @@ describe('Image metadata', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('keep existing ICC profile', async () => {
|
||||
const data = await sharp(fixtures.inputJpgWithExif)
|
||||
.keepIccProfile()
|
||||
.toBuffer();
|
||||
|
||||
const metadata = await sharp(data).metadata();
|
||||
const { description } = icc.parse(metadata.icc);
|
||||
assert.strictEqual(description, 'Generic RGB Profile');
|
||||
});
|
||||
|
||||
it('keep existing ICC profile, ignore colourspace conversion', async () => {
|
||||
const data = await sharp(fixtures.inputJpgWithExif)
|
||||
.keepIccProfile()
|
||||
.toColourspace('cmyk')
|
||||
.toBuffer();
|
||||
|
||||
const metadata = await sharp(data).metadata();
|
||||
assert.strictEqual(metadata.channels, 3);
|
||||
const { description } = icc.parse(metadata.icc);
|
||||
assert.strictEqual(description, 'Generic RGB Profile');
|
||||
});
|
||||
|
||||
it('transform to ICC profile and attach', async () => {
|
||||
const data = await sharp({ create })
|
||||
.png()
|
||||
.withIccProfile('p3', { attach: true })
|
||||
.toBuffer();
|
||||
|
||||
const metadata = await sharp(data).metadata();
|
||||
const { description } = icc.parse(metadata.icc);
|
||||
assert.strictEqual(description, 'sP3C');
|
||||
});
|
||||
|
||||
it('transform to ICC profile but do not attach', async () => {
|
||||
const data = await sharp({ create })
|
||||
.png()
|
||||
.withIccProfile('p3', { attach: false })
|
||||
.toBuffer();
|
||||
|
||||
const metadata = await sharp(data).metadata();
|
||||
assert.strictEqual(3, metadata.channels);
|
||||
assert.strictEqual(undefined, metadata.icc);
|
||||
});
|
||||
|
||||
it('Apply CMYK output ICC profile', function (done) {
|
||||
const output = fixtures.path('output.icc-cmyk.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(64)
|
||||
.withMetadata({ icc: 'cmyk' })
|
||||
.withIccProfile('cmyk')
|
||||
.toFile(output, function (err) {
|
||||
if (err) throw err;
|
||||
sharp(output).metadata(function (err, metadata) {
|
||||
@@ -581,7 +627,7 @@ describe('Image metadata', function () {
|
||||
const output = fixtures.path('output.hilutite.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(64)
|
||||
.withMetadata({ icc: fixtures.path('hilutite.icm') })
|
||||
.withIccProfile(fixtures.path('hilutite.icm'))
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('hilutite.jpg'), 9);
|
||||
@@ -620,7 +666,6 @@ describe('Image metadata', function () {
|
||||
it('Remove EXIF metadata after a resize', function (done) {
|
||||
sharp(fixtures.inputJpgWithExif)
|
||||
.resize(320, 240)
|
||||
.withMetadata(false)
|
||||
.toBuffer(function (err, buffer) {
|
||||
if (err) throw err;
|
||||
sharp(buffer).metadata(function (err, metadata) {
|
||||
@@ -651,14 +696,7 @@ describe('Image metadata', function () {
|
||||
});
|
||||
|
||||
it('Add EXIF metadata to JPEG', async () => {
|
||||
const data = await sharp({
|
||||
create: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
channels: 3,
|
||||
background: 'red'
|
||||
}
|
||||
})
|
||||
const data = await sharp({ create })
|
||||
.jpeg()
|
||||
.withMetadata({
|
||||
exif: {
|
||||
@@ -675,14 +713,7 @@ describe('Image metadata', function () {
|
||||
});
|
||||
|
||||
it('Set density of JPEG', async () => {
|
||||
const data = await sharp({
|
||||
create: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
channels: 3,
|
||||
background: 'red'
|
||||
}
|
||||
})
|
||||
const data = await sharp({ create })
|
||||
.withMetadata({
|
||||
density: 300
|
||||
})
|
||||
@@ -694,14 +725,7 @@ describe('Image metadata', function () {
|
||||
});
|
||||
|
||||
it('Set density of PNG', async () => {
|
||||
const data = await sharp({
|
||||
create: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
channels: 3,
|
||||
background: 'red'
|
||||
}
|
||||
})
|
||||
const data = await sharp({ create })
|
||||
.withMetadata({
|
||||
density: 96
|
||||
})
|
||||
@@ -809,11 +833,7 @@ describe('Image metadata', function () {
|
||||
});
|
||||
|
||||
it('withMetadata adds default sRGB profile to RGB16', async () => {
|
||||
const data = await sharp({
|
||||
create: {
|
||||
width: 8, height: 8, channels: 4, background: 'orange'
|
||||
}
|
||||
})
|
||||
const data = await sharp({ create })
|
||||
.toColorspace('rgb16')
|
||||
.png()
|
||||
.withMetadata()
|
||||
@@ -827,11 +847,7 @@ describe('Image metadata', function () {
|
||||
});
|
||||
|
||||
it('withMetadata adds P3 profile to 16-bit PNG', async () => {
|
||||
const data = await sharp({
|
||||
create: {
|
||||
width: 8, height: 8, channels: 4, background: 'orange'
|
||||
}
|
||||
})
|
||||
const data = await sharp({ create })
|
||||
.toColorspace('rgb16')
|
||||
.png()
|
||||
.withMetadata({ icc: 'p3' })
|
||||
@@ -871,7 +887,89 @@ describe('Image metadata', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid withMetadata parameters', function () {
|
||||
it('keepExif maintains all EXIF metadata', async () => {
|
||||
const data1 = await sharp({ create })
|
||||
.withExif({
|
||||
IFD0: {
|
||||
Copyright: 'Test 1',
|
||||
Software: 'sharp'
|
||||
}
|
||||
})
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
|
||||
const data2 = await sharp(data1)
|
||||
.keepExif()
|
||||
.toBuffer();
|
||||
|
||||
const md2 = await sharp(data2).metadata();
|
||||
const exif2 = exifReader(md2.exif);
|
||||
assert.strictEqual(exif2.Image.Copyright, 'Test 1');
|
||||
assert.strictEqual(exif2.Image.Software, 'sharp');
|
||||
});
|
||||
|
||||
it('withExif replaces all EXIF metadata', async () => {
|
||||
const data1 = await sharp({ create })
|
||||
.withExif({
|
||||
IFD0: {
|
||||
Copyright: 'Test 1',
|
||||
Software: 'sharp'
|
||||
}
|
||||
})
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
|
||||
const md1 = await sharp(data1).metadata();
|
||||
const exif1 = exifReader(md1.exif);
|
||||
assert.strictEqual(exif1.Image.Copyright, 'Test 1');
|
||||
assert.strictEqual(exif1.Image.Software, 'sharp');
|
||||
|
||||
const data2 = await sharp(data1)
|
||||
.withExif({
|
||||
IFD0: {
|
||||
Copyright: 'Test 2'
|
||||
}
|
||||
})
|
||||
.toBuffer();
|
||||
|
||||
const md2 = await sharp(data2).metadata();
|
||||
const exif2 = exifReader(md2.exif);
|
||||
assert.strictEqual(exif2.Image.Copyright, 'Test 2');
|
||||
assert.strictEqual(exif2.Image.Software, undefined);
|
||||
});
|
||||
|
||||
it('withExifMerge merges all EXIF metadata', async () => {
|
||||
const data1 = await sharp({ create })
|
||||
.withExif({
|
||||
IFD0: {
|
||||
Copyright: 'Test 1'
|
||||
}
|
||||
})
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
|
||||
const md1 = await sharp(data1).metadata();
|
||||
const exif1 = exifReader(md1.exif);
|
||||
assert.strictEqual(exif1.Image.Copyright, 'Test 1');
|
||||
assert.strictEqual(exif1.Image.Software, undefined);
|
||||
|
||||
const data2 = await sharp(data1)
|
||||
.withExifMerge({
|
||||
IFD0: {
|
||||
Copyright: 'Test 2',
|
||||
Software: 'sharp'
|
||||
|
||||
}
|
||||
})
|
||||
.toBuffer();
|
||||
|
||||
const md2 = await sharp(data2).metadata();
|
||||
const exif2 = exifReader(md2.exif);
|
||||
assert.strictEqual(exif2.Image.Copyright, 'Test 2');
|
||||
assert.strictEqual(exif2.Image.Software, 'sharp');
|
||||
});
|
||||
|
||||
describe('Invalid parameters', function () {
|
||||
it('String orientation', function () {
|
||||
assert.throws(function () {
|
||||
sharp().withMetadata({ orientation: 'zoinks' });
|
||||
@@ -922,5 +1020,22 @@ describe('Image metadata', function () {
|
||||
sharp().withMetadata({ exif: { ifd0: { fail: false } } });
|
||||
});
|
||||
});
|
||||
it('withIccProfile invalid profile', () => {
|
||||
assert.throws(
|
||||
() => sharp().withIccProfile(false),
|
||||
/Expected string for icc but received false of type boolean/
|
||||
);
|
||||
});
|
||||
it('withIccProfile missing attach', () => {
|
||||
assert.doesNotThrow(
|
||||
() => sharp().withIccProfile('test', {})
|
||||
);
|
||||
});
|
||||
it('withIccProfile invalid attach', () => {
|
||||
assert.throws(
|
||||
() => sharp().withIccProfile('test', { attach: 1 }),
|
||||
/Expected boolean for attach but received 1 of type number/
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,9 +58,9 @@ describe('Text to image', function () {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(3, info.channels);
|
||||
assert.ok(inRange(info.width, 450, 550), `Actual width ${info.width}`);
|
||||
assert.ok(inRange(info.height, 300, 450), `Actual height ${info.height}`);
|
||||
assert.ok(inRange(info.textAutofitDpi, 900, 1200), `Actual textAutofitDpi ${info.textAutofitDpi}`);
|
||||
assert.ok(inRange(info.width, 400, 600), `Actual width ${info.width}`);
|
||||
assert.ok(inRange(info.height, 300, 500), `Actual height ${info.height}`);
|
||||
assert.ok(inRange(info.textAutofitDpi, 900, 1300), `Actual textAutofitDpi ${info.textAutofitDpi}`);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,16 +8,19 @@ const assert = require('assert');
|
||||
const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
// Allow for small rounding differences between platforms
|
||||
const maxDistance = 6;
|
||||
|
||||
describe('Tint', function () {
|
||||
it('tints rgb image red', function (done) {
|
||||
const output = fixtures.path('output.tint-red.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
||||
.resize(320, 240)
|
||||
.tint('#FF0000')
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-red.jpg'), 18);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-red.jpg'), maxDistance);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -25,12 +28,12 @@ describe('Tint', function () {
|
||||
it('tints rgb image green', function (done) {
|
||||
const output = fixtures.path('output.tint-green.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
||||
.resize(320, 240)
|
||||
.tint('#00FF00')
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-green.jpg'), 27);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-green.jpg'), maxDistance);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -38,12 +41,12 @@ describe('Tint', function () {
|
||||
it('tints rgb image blue', function (done) {
|
||||
const output = fixtures.path('output.tint-blue.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
||||
.resize(320, 240)
|
||||
.tint('#0000FF')
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-blue.jpg'), 14);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-blue.jpg'), maxDistance);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -51,13 +54,13 @@ describe('Tint', function () {
|
||||
it('tints rgb image with sepia tone', function (done) {
|
||||
const output = fixtures.path('output.tint-sepia-hex.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
||||
.resize(320, 240)
|
||||
.tint('#704214')
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-sepia.jpg'), 10);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-sepia.jpg'), maxDistance);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -65,13 +68,13 @@ describe('Tint', function () {
|
||||
it('tints rgb image with sepia tone with rgb colour', function (done) {
|
||||
const output = fixtures.path('output.tint-sepia-rgb.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
||||
.resize(320, 240)
|
||||
.tint([112, 66, 20])
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-sepia.jpg'), 10);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-sepia.jpg'), maxDistance);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -79,13 +82,13 @@ describe('Tint', function () {
|
||||
it('tints rgb image with alpha channel', function (done) {
|
||||
const output = fixtures.path('output.tint-alpha.png');
|
||||
sharp(fixtures.inputPngRGBWithAlpha)
|
||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
||||
.resize(320, 240)
|
||||
.tint('#704214')
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-alpha.png'), 10);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-alpha.png'), maxDistance);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -93,12 +96,12 @@ describe('Tint', function () {
|
||||
it('tints cmyk image red', function (done) {
|
||||
const output = fixtures.path('output.tint-cmyk.jpg');
|
||||
sharp(fixtures.inputJpgWithCmykProfile)
|
||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
||||
.resize(320, 240)
|
||||
.tint('#FF0000')
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-cmyk.jpg'), 15);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-cmyk.jpg'), maxDistance);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||