Skip to content

Commit

Permalink
Increase control over output metadata (#3856)
Browse files Browse the repository at this point in the history
Add withX and keepX functions to take advantage of
libvips 8.15.0 new 'keep' metadata feature.
  • Loading branch information
lovell committed Nov 22, 2023
1 parent 3f7313d commit e78200c
Show file tree
Hide file tree
Showing 13 changed files with 689 additions and 174 deletions.
2 changes: 1 addition & 1 deletion docs/api-colour.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ An alpha channel may be present and will be unchanged by the operation.

| Param | Type | Description |
| --- | --- | --- |
| tint | <code>String</code> \| <code>Object</code> | Parsed by the [color](https://www.npmjs.org/package/color) module. |
| tint | <code>string</code> \| <code>Object</code> | Parsed by the [color](https://www.npmjs.org/package/color) module. |

**Example**
```js
Expand Down
180 changes: 149 additions & 31 deletions docs/api-output.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,56 +111,174 @@ await sharp(pixelArray, { raw: { width, height, channels } })
```


## withMetadata
> withMetadata([options]) ⇒ <code>Sharp</code>
## keepExif
> keepExif() ⇒ <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.

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.


**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.&lt;string, Object.&lt;string, string&gt;&gt;</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.&lt;string, Object.&lt;string, string&gt;&gt;</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.orientation] | <code>number</code> | | value between 1 and 8, used to update the EXIF `Orientation` tag. |
| [options.icc] | <code>string</code> | <code>&quot;&#x27;srgb&#x27;&quot;</code> | Filesystem path to output ICC profile, relative to `process.cwd()`, defaults to built-in sRGB. |
| [options.exif] | <code>Object.&lt;Object&gt;</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). |
| [options.attach] | <code>number</code> | <code>true</code> | Should the ICC profile be included in the output image metadata? |

**Example**
```js
sharp('input.jpg')
.withMetadata()
.toFile('output-with-metadata.jpg')
.then(info => { ... });
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
// 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 outputWithMetadata = await sharp(inputWithMetadata)
.keepMetadata()
.toBuffer();
```


## withMetadata
> withMetadata([options]) ⇒ <code>Sharp</code>
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.


**Throws**:

- <code>Error</code> Invalid parameters


| 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
const outputSrgbWithMetadata = await sharp(inputRgbWithMetadata)
.withMetadata()
.toBuffer();
```
**Example**
Expand Down
5 changes: 5 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ 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)

Expand Down Expand Up @@ -41,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)

Expand Down
2 changes: 1 addition & 1 deletion docs/search-index.json

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions lib/constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,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,
Expand Down
71 changes: 65 additions & 6 deletions lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,14 +633,51 @@ 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.
* This will also convert to and add a web-friendly sRGB ICC profile.
* @param withMetadata
* @throws {Error} Invalid parameters.
*/
withMetadata(withMetadata?: boolean | WriteableMetadata): Sharp;
withMetadata(withMetadata?: WriteableMetadata): Sharp;

/**
* Use these JPEG options for output image.
Expand Down Expand Up @@ -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 {
/** 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. */
/**
* 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. (optional, default {}) */
exif?: Record<string, any> | undefined;
/** Number of pixels per inch (DPI) */
density?: number | 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 {
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit e78200c

Please sign in to comment.