mirror of
https://github.com/lovell/sharp.git
synced 2026-02-11 09:06:15 +01:00
Compare commits
7 Commits
v0.34.1
...
73bec629cf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73bec629cf | ||
|
|
758d7e63cc | ||
|
|
eba3e9aeb2 | ||
|
|
701143afb3 | ||
|
|
38b6f44611 | ||
|
|
5b5dfbad77 | ||
|
|
a642767329 |
@@ -1,6 +1,6 @@
|
|||||||
# sharp
|
# sharp
|
||||||
|
|
||||||
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/public/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
|
<img src="https://sharp.pixelplumbing.com/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
|
||||||
|
|
||||||
The typical use case for this high speed Node-API module
|
The typical use case for this high speed Node-API module
|
||||||
is to convert large images in common formats to
|
is to convert large images in common formats to
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default defineConfig({
|
|||||||
tag: 'meta',
|
tag: 'meta',
|
||||||
attrs: {
|
attrs: {
|
||||||
'http-equiv': 'Content-Security-Policy',
|
'http-equiv': 'Content-Security-Policy',
|
||||||
content: "default-src 'self'; connect-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://cdn.jsdelivr.net/gh/lovell/; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://static.cloudflareinsights.com/beacon.min.js/;"
|
content: "default-src 'self'; connect-src 'self'; object-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://static.cloudflareinsights.com/beacon.min.js/;"
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
tag: 'link',
|
tag: 'link',
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ When both a `width` and `height` are provided, the possible methods by which the
|
|||||||
|
|
||||||
Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
|
Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
|
||||||
|
|
||||||
<img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.svg">
|
<img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="/api-resize-fit.svg">
|
||||||
|
|
||||||
When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
|
When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
|
||||||
- `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
|
- `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
|
||||||
|
|||||||
@@ -6,6 +6,18 @@ title: Changelog
|
|||||||
|
|
||||||
Requires libvips v8.16.1
|
Requires libvips v8.16.1
|
||||||
|
|
||||||
|
### v0.34.2 - TBD
|
||||||
|
|
||||||
|
* Ensure animated GIF to WebP conversion retains loop (regression in 0.34.0).
|
||||||
|
[#3394](https://github.com/lovell/sharp/issues/3394)
|
||||||
|
|
||||||
|
* Ensure `pdfBackground` constructor property is used.
|
||||||
|
[#4207](https://github.com/lovell/sharp/pull/4207)
|
||||||
|
|
||||||
|
* TypeScript: Ensure `smartDeblock` property is included in WebP definition.
|
||||||
|
[#4387](https://github.com/lovell/sharp/pull/4387)
|
||||||
|
[@Stephen-X](https://github.com/Stephen-X)
|
||||||
|
|
||||||
### v0.34.1 - 7th April 2025
|
### v0.34.1 - 7th April 2025
|
||||||
|
|
||||||
* TypeScript: Ensure new `autoOrient` property is optional.
|
* TypeScript: Ensure new `autoOrient` property is optional.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
title: "High performance Node.js image processing"
|
title: "High performance Node.js image processing"
|
||||||
---
|
---
|
||||||
|
|
||||||
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/public/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
|
<img src="/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
|
||||||
|
|
||||||
The typical use case for this high speed Node-API module
|
The typical use case for this high speed Node-API module
|
||||||
is to convert large images in common formats to
|
is to convert large images in common formats to
|
||||||
|
|||||||
@@ -135,17 +135,15 @@ function toColorspace (colorspace) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a colour attribute of the this.options Object.
|
* Create a RGBA colour array from a given value.
|
||||||
* @private
|
* @private
|
||||||
* @param {string} key
|
|
||||||
* @param {string|Object} value
|
* @param {string|Object} value
|
||||||
* @throws {Error} Invalid value
|
* @throws {Error} Invalid value
|
||||||
*/
|
*/
|
||||||
function _setBackgroundColourOption (key, value) {
|
function _getBackgroundColourOption (value) {
|
||||||
if (is.defined(value)) {
|
|
||||||
if (is.object(value) || is.string(value)) {
|
if (is.object(value) || is.string(value)) {
|
||||||
const colour = color(value);
|
const colour = color(value);
|
||||||
this.options[key] = [
|
return [
|
||||||
colour.red(),
|
colour.red(),
|
||||||
colour.green(),
|
colour.green(),
|
||||||
colour.blue(),
|
colour.blue(),
|
||||||
@@ -155,6 +153,18 @@ function _setBackgroundColourOption (key, value) {
|
|||||||
throw is.invalidParameterError('background', 'object or string', value);
|
throw is.invalidParameterError('background', 'object or string', value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a colour attribute of the this.options Object.
|
||||||
|
* @private
|
||||||
|
* @param {string} key
|
||||||
|
* @param {string|Object} value
|
||||||
|
* @throws {Error} Invalid value
|
||||||
|
*/
|
||||||
|
function _setBackgroundColourOption (key, value) {
|
||||||
|
if (is.defined(value)) {
|
||||||
|
this.options[key] = _getBackgroundColourOption(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -173,6 +183,7 @@ module.exports = function (Sharp) {
|
|||||||
toColourspace,
|
toColourspace,
|
||||||
toColorspace,
|
toColorspace,
|
||||||
// Private
|
// Private
|
||||||
|
_getBackgroundColourOption,
|
||||||
_setBackgroundColourOption
|
_setBackgroundColourOption
|
||||||
});
|
});
|
||||||
// Class attributes
|
// Class attributes
|
||||||
|
|||||||
@@ -298,7 +298,7 @@ const Sharp = function (input, options) {
|
|||||||
withExif: {},
|
withExif: {},
|
||||||
withExifMerge: true,
|
withExifMerge: true,
|
||||||
resolveWithObject: false,
|
resolveWithObject: false,
|
||||||
loop: 1,
|
loop: -1,
|
||||||
delay: [],
|
delay: [],
|
||||||
// output format
|
// output format
|
||||||
jpegQuality: 80,
|
jpegQuality: 80,
|
||||||
|
|||||||
6
lib/index.d.ts
vendored
6
lib/index.d.ts
vendored
@@ -1336,6 +1336,8 @@ declare namespace sharp {
|
|||||||
nearLossless?: boolean | undefined;
|
nearLossless?: boolean | undefined;
|
||||||
/** Use high quality chroma subsampling (optional, default false) */
|
/** Use high quality chroma subsampling (optional, default false) */
|
||||||
smartSubsample?: boolean | undefined;
|
smartSubsample?: boolean | undefined;
|
||||||
|
/** Auto-adjust the deblocking filter, slow but can improve low contrast edges (optional, default false) */
|
||||||
|
smartDeblock?: boolean | undefined;
|
||||||
/** Level of CPU effort to reduce file size, integer 0-6 (optional, default 4) */
|
/** Level of CPU effort to reduce file size, integer 0-6 (optional, default 4) */
|
||||||
effort?: number | undefined;
|
effort?: number | undefined;
|
||||||
/** Prevent use of animation key frames to minimise file size (slow) (optional, default false) */
|
/** Prevent use of animation key frames to minimise file size (slow) (optional, default false) */
|
||||||
@@ -1699,6 +1701,10 @@ declare namespace sharp {
|
|||||||
/** When using the attention crop strategy, the focal point of the cropped region */
|
/** When using the attention crop strategy, the focal point of the cropped region */
|
||||||
attentionX?: number | undefined;
|
attentionX?: number | undefined;
|
||||||
attentionY?: number | undefined;
|
attentionY?: number | undefined;
|
||||||
|
/** Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP */
|
||||||
|
pages?: number | undefined;
|
||||||
|
/** Number of pixels high each page in a multi-page image will be. */
|
||||||
|
pageHeight?: number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AvailableFormatInfo {
|
interface AvailableFormatInfo {
|
||||||
|
|||||||
19
lib/input.js
19
lib/input.js
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const color = require('color');
|
|
||||||
const is = require('./is');
|
const is = require('./is');
|
||||||
const sharp = require('./sharp');
|
const sharp = require('./sharp');
|
||||||
|
|
||||||
@@ -249,7 +248,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|||||||
}
|
}
|
||||||
// PDF background colour
|
// PDF background colour
|
||||||
if (is.defined(inputOptions.pdfBackground)) {
|
if (is.defined(inputOptions.pdfBackground)) {
|
||||||
this._setBackgroundColourOption('pdfBackground', inputOptions.pdfBackground);
|
inputDescriptor.pdfBackground = this._getBackgroundColourOption(inputOptions.pdfBackground);
|
||||||
}
|
}
|
||||||
// Create new image
|
// Create new image
|
||||||
if (is.defined(inputOptions.create)) {
|
if (is.defined(inputOptions.create)) {
|
||||||
@@ -288,13 +287,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|||||||
if (!is.inRange(inputOptions.create.channels, 3, 4)) {
|
if (!is.inRange(inputOptions.create.channels, 3, 4)) {
|
||||||
throw is.invalidParameterError('create.channels', 'number between 3 and 4', inputOptions.create.channels);
|
throw is.invalidParameterError('create.channels', 'number between 3 and 4', inputOptions.create.channels);
|
||||||
}
|
}
|
||||||
const background = color(inputOptions.create.background);
|
inputDescriptor.createBackground = this._getBackgroundColourOption(inputOptions.create.background);
|
||||||
inputDescriptor.createBackground = [
|
|
||||||
background.red(),
|
|
||||||
background.green(),
|
|
||||||
background.blue(),
|
|
||||||
Math.round(background.alpha() * 255)
|
|
||||||
];
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Expected valid noise or background to create a new input image');
|
throw new Error('Expected valid noise or background to create a new input image');
|
||||||
}
|
}
|
||||||
@@ -410,13 +403,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(inputOptions.join.background)) {
|
if (is.defined(inputOptions.join.background)) {
|
||||||
const background = color(inputOptions.join.background);
|
inputDescriptor.joinBackground = this._getBackgroundColourOption(inputOptions.join.background);
|
||||||
inputDescriptor.joinBackground = [
|
|
||||||
background.red(),
|
|
||||||
background.green(),
|
|
||||||
background.blue(),
|
|
||||||
Math.round(background.alpha() * 255)
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
if (is.defined(inputOptions.join.halign)) {
|
if (is.defined(inputOptions.join.halign)) {
|
||||||
if (is.string(inputOptions.join.halign) && is.string(this.constructor.align[inputOptions.join.halign])) {
|
if (is.string(inputOptions.join.halign) && is.string(this.constructor.align[inputOptions.join.halign])) {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const color = require('color');
|
|
||||||
const is = require('./is');
|
const is = require('./is');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,13 +66,7 @@ function rotate (angle, options) {
|
|||||||
} else if (is.number(angle)) {
|
} else if (is.number(angle)) {
|
||||||
this.options.rotationAngle = angle;
|
this.options.rotationAngle = angle;
|
||||||
if (is.object(options) && options.background) {
|
if (is.object(options) && options.background) {
|
||||||
const backgroundColour = color(options.background);
|
this._setBackgroundColourOption('rotationBackground', options.background);
|
||||||
this.options.rotationBackground = [
|
|
||||||
backgroundColour.red(),
|
|
||||||
backgroundColour.green(),
|
|
||||||
backgroundColour.blue(),
|
|
||||||
Math.round(backgroundColour.alpha() * 255)
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('angle', 'numeric', angle);
|
throw is.invalidParameterError('angle', 'numeric', angle);
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ function isResizeExpected (options) {
|
|||||||
*
|
*
|
||||||
* Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
|
* Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
|
||||||
*
|
*
|
||||||
* <img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.svg">
|
* <img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="/api-resize-fit.svg">
|
||||||
*
|
*
|
||||||
* When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
|
* When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
|
||||||
* - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
|
* - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
|
||||||
|
|||||||
@@ -651,22 +651,21 @@ namespace sharp {
|
|||||||
*/
|
*/
|
||||||
VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop) {
|
VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop) {
|
||||||
bool hasDelay = !delay.empty();
|
bool hasDelay = !delay.empty();
|
||||||
|
|
||||||
// Avoid a copy if none of the animation properties are needed.
|
|
||||||
if (nPages == 1 && !hasDelay && loop == -1) return image;
|
|
||||||
|
|
||||||
if (delay.size() == 1) {
|
|
||||||
// We have just one delay, repeat that value for all frames.
|
|
||||||
delay.insert(delay.end(), nPages - 1, delay[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attaching metadata, need to copy the image.
|
|
||||||
VImage copy = image.copy();
|
VImage copy = image.copy();
|
||||||
|
|
||||||
// Only set page-height if we have more than one page, or this could
|
// Only set page-height if we have more than one page, or this could
|
||||||
// accidentally turn into an animated image later.
|
// accidentally turn into an animated image later.
|
||||||
if (nPages > 1) copy.set(VIPS_META_PAGE_HEIGHT, pageHeight);
|
if (nPages > 1) copy.set(VIPS_META_PAGE_HEIGHT, pageHeight);
|
||||||
if (hasDelay) copy.set("delay", delay);
|
if (hasDelay) {
|
||||||
|
if (delay.size() == 1) {
|
||||||
|
// We have just one delay, repeat that value for all frames.
|
||||||
|
delay.insert(delay.end(), nPages - 1, delay[0]);
|
||||||
|
}
|
||||||
|
copy.set("delay", delay);
|
||||||
|
}
|
||||||
|
if (nPages == 1 && !hasDelay && loop == -1) {
|
||||||
|
loop = 1;
|
||||||
|
}
|
||||||
if (loop != -1) copy.set("loop", loop);
|
if (loop != -1) copy.set("loop", loop);
|
||||||
|
|
||||||
return copy;
|
return copy;
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ struct PipelineBaton {
|
|||||||
ensureAlpha(-1.0),
|
ensureAlpha(-1.0),
|
||||||
colourspacePipeline(VIPS_INTERPRETATION_LAST),
|
colourspacePipeline(VIPS_INTERPRETATION_LAST),
|
||||||
colourspace(VIPS_INTERPRETATION_LAST),
|
colourspace(VIPS_INTERPRETATION_LAST),
|
||||||
loop(1),
|
loop(-1),
|
||||||
tileSize(256),
|
tileSize(256),
|
||||||
tileOverlap(0),
|
tileOverlap(0),
|
||||||
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
|
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
|
||||||
|
|||||||
@@ -238,4 +238,15 @@ describe('GIF input', () => {
|
|||||||
assert.strictEqual(1, loop);
|
assert.strictEqual(1, loop);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Animated GIF to animated WebP merges identical frames', async () => {
|
||||||
|
const webp = await sharp(fixtures.inputGifAnimated, { animated: true })
|
||||||
|
.webp()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
const { delay, loop, pages } = await sharp(webp).metadata();
|
||||||
|
assert.deepStrictEqual([120, 120, 90, 120, 120, 90, 120, 90, 30], delay);
|
||||||
|
assert.strictEqual(0, loop);
|
||||||
|
assert.strictEqual(9, pages);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user