mirror of
https://github.com/lovell/sharp.git
synced 2026-02-11 09:06:15 +01:00
Compare commits
4 Commits
v0.34.1
...
701143afb3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
701143afb3 | ||
|
|
38b6f44611 | ||
|
|
5b5dfbad77 | ||
|
|
a642767329 |
@@ -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.
|
||||
|
||||
<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="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/public/api-resize-fit.svg">
|
||||
|
||||
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`.
|
||||
|
||||
@@ -6,6 +6,14 @@ title: Changelog
|
||||
|
||||
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)
|
||||
|
||||
### v0.34.1 - 7th April 2025
|
||||
|
||||
* TypeScript: Ensure new `autoOrient` property is optional.
|
||||
|
||||
@@ -134,6 +134,26 @@ function toColorspace (colorspace) {
|
||||
return this.toColourspace(colorspace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a RGBA colour array from a given value.
|
||||
* @private
|
||||
* @param {string|Object} value
|
||||
* @throws {Error} Invalid value
|
||||
*/
|
||||
function _getBackgroundColourOption (value) {
|
||||
if (is.object(value) || is.string(value)) {
|
||||
const colour = color(value);
|
||||
return [
|
||||
colour.red(),
|
||||
colour.green(),
|
||||
colour.blue(),
|
||||
Math.round(colour.alpha() * 255)
|
||||
];
|
||||
} else {
|
||||
throw is.invalidParameterError('background', 'object or string', value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a colour attribute of the this.options Object.
|
||||
* @private
|
||||
@@ -143,17 +163,7 @@ function toColorspace (colorspace) {
|
||||
*/
|
||||
function _setBackgroundColourOption (key, value) {
|
||||
if (is.defined(value)) {
|
||||
if (is.object(value) || is.string(value)) {
|
||||
const colour = color(value);
|
||||
this.options[key] = [
|
||||
colour.red(),
|
||||
colour.green(),
|
||||
colour.blue(),
|
||||
Math.round(colour.alpha() * 255)
|
||||
];
|
||||
} else {
|
||||
throw is.invalidParameterError('background', 'object or string', value);
|
||||
}
|
||||
this.options[key] = _getBackgroundColourOption(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,6 +183,7 @@ module.exports = function (Sharp) {
|
||||
toColourspace,
|
||||
toColorspace,
|
||||
// Private
|
||||
_getBackgroundColourOption,
|
||||
_setBackgroundColourOption
|
||||
});
|
||||
// Class attributes
|
||||
|
||||
@@ -298,7 +298,7 @@ const Sharp = function (input, options) {
|
||||
withExif: {},
|
||||
withExifMerge: true,
|
||||
resolveWithObject: false,
|
||||
loop: 1,
|
||||
loop: -1,
|
||||
delay: [],
|
||||
// output format
|
||||
jpegQuality: 80,
|
||||
|
||||
4
lib/index.d.ts
vendored
4
lib/index.d.ts
vendored
@@ -1699,6 +1699,10 @@ declare namespace sharp {
|
||||
/** When using the attention crop strategy, the focal point of the cropped region */
|
||||
attentionX?: 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 {
|
||||
|
||||
19
lib/input.js
19
lib/input.js
@@ -3,7 +3,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const color = require('color');
|
||||
const is = require('./is');
|
||||
const sharp = require('./sharp');
|
||||
|
||||
@@ -249,7 +248,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
}
|
||||
// PDF background colour
|
||||
if (is.defined(inputOptions.pdfBackground)) {
|
||||
this._setBackgroundColourOption('pdfBackground', inputOptions.pdfBackground);
|
||||
inputDescriptor.pdfBackground = this._getBackgroundColourOption(inputOptions.pdfBackground);
|
||||
}
|
||||
// Create new image
|
||||
if (is.defined(inputOptions.create)) {
|
||||
@@ -288,13 +287,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
if (!is.inRange(inputOptions.create.channels, 3, 4)) {
|
||||
throw is.invalidParameterError('create.channels', 'number between 3 and 4', inputOptions.create.channels);
|
||||
}
|
||||
const background = color(inputOptions.create.background);
|
||||
inputDescriptor.createBackground = [
|
||||
background.red(),
|
||||
background.green(),
|
||||
background.blue(),
|
||||
Math.round(background.alpha() * 255)
|
||||
];
|
||||
inputDescriptor.createBackground = this._getBackgroundColourOption(inputOptions.create.background);
|
||||
} else {
|
||||
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)) {
|
||||
const background = color(inputOptions.join.background);
|
||||
inputDescriptor.joinBackground = [
|
||||
background.red(),
|
||||
background.green(),
|
||||
background.blue(),
|
||||
Math.round(background.alpha() * 255)
|
||||
];
|
||||
inputDescriptor.joinBackground = this._getBackgroundColourOption(inputOptions.join.background);
|
||||
}
|
||||
if (is.defined(inputOptions.join.halign)) {
|
||||
if (is.string(inputOptions.join.halign) && is.string(this.constructor.align[inputOptions.join.halign])) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const color = require('color');
|
||||
const is = require('./is');
|
||||
|
||||
/**
|
||||
@@ -67,13 +66,7 @@ function rotate (angle, options) {
|
||||
} else if (is.number(angle)) {
|
||||
this.options.rotationAngle = angle;
|
||||
if (is.object(options) && options.background) {
|
||||
const backgroundColour = color(options.background);
|
||||
this.options.rotationBackground = [
|
||||
backgroundColour.red(),
|
||||
backgroundColour.green(),
|
||||
backgroundColour.blue(),
|
||||
Math.round(backgroundColour.alpha() * 255)
|
||||
];
|
||||
this._setBackgroundColourOption('rotationBackground', options.background);
|
||||
}
|
||||
} else {
|
||||
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.
|
||||
*
|
||||
* <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="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/public/api-resize-fit.svg">
|
||||
*
|
||||
* 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`.
|
||||
|
||||
@@ -651,22 +651,21 @@ namespace sharp {
|
||||
*/
|
||||
VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop) {
|
||||
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();
|
||||
|
||||
// Only set page-height if we have more than one page, or this could
|
||||
// accidentally turn into an animated image later.
|
||||
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);
|
||||
|
||||
return copy;
|
||||
|
||||
@@ -384,7 +384,7 @@ struct PipelineBaton {
|
||||
ensureAlpha(-1.0),
|
||||
colourspacePipeline(VIPS_INTERPRETATION_LAST),
|
||||
colourspace(VIPS_INTERPRETATION_LAST),
|
||||
loop(1),
|
||||
loop(-1),
|
||||
tileSize(256),
|
||||
tileOverlap(0),
|
||||
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
|
||||
|
||||
@@ -238,4 +238,15 @@ describe('GIF input', () => {
|
||||
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