mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 02:30:12 +02:00
Add pageHeight param to create/new for animated input #3236
This commit is contained in:
parent
852c7f8663
commit
e26d4e9d5b
@ -50,15 +50,17 @@ where the overall height is the `pageHeight` multiplied by the number of `pages`
|
||||
| [options.raw.height] | <code>number</code> | | integral number of pixels high. |
|
||||
| [options.raw.channels] | <code>number</code> | | integral number of channels, between 1 and 4. |
|
||||
| [options.raw.premultiplied] | <code>boolean</code> | | specifies that the raw input has already been premultiplied, set to `true` to avoid sharp premultiplying the image. (optional, default `false`) |
|
||||
| [options.raw.pageHeight] | <code>number</code> | | The pixel height of each page/frame for animated images, must be an integral factor of `raw.height`. |
|
||||
| [options.create] | <code>Object</code> | | describes a new image to be created. |
|
||||
| [options.create.width] | <code>number</code> | | integral number of pixels wide. |
|
||||
| [options.create.height] | <code>number</code> | | integral number of pixels high. |
|
||||
| [options.create.channels] | <code>number</code> | | integral number of channels, either 3 (RGB) or 4 (RGBA). |
|
||||
| [options.create.background] | <code>string</code> \| <code>Object</code> | | parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. |
|
||||
| [options.create.pageHeight] | <code>number</code> | | The pixel height of each page/frame for animated images, must be an integral factor of `create.height`. |
|
||||
| [options.create.noise] | <code>Object</code> | | describes a noise to be created. |
|
||||
| [options.create.noise.type] | <code>string</code> | | type of generated noise, currently only `gaussian` is supported. |
|
||||
| [options.create.noise.mean] | <code>number</code> | | mean of pixels in generated noise. |
|
||||
| [options.create.noise.sigma] | <code>number</code> | | standard deviation of pixels in generated noise. |
|
||||
| [options.create.noise.mean] | <code>number</code> | <code>128</code> | Mean value of pixels in the generated noise. |
|
||||
| [options.create.noise.sigma] | <code>number</code> | <code>30</code> | Standard deviation of pixel values in the generated noise. |
|
||||
| [options.text] | <code>Object</code> | | describes a new text image to be created. |
|
||||
| [options.text.text] | <code>string</code> | | text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`. |
|
||||
| [options.text.font] | <code>string</code> | | font name to render with. |
|
||||
|
@ -18,6 +18,9 @@ Requires libvips v8.17.0
|
||||
|
||||
* Expose `keepDuplicateFrames` GIF output parameter.
|
||||
|
||||
* Add `pageHeight` option to `create` and `raw` input for animated images.
|
||||
[#3236](https://github.com/lovell/sharp/issues/3236)
|
||||
|
||||
* Expose JPEG 2000 `oneshot` decoder option.
|
||||
[#4262](https://github.com/lovell/sharp/pull/4262)
|
||||
[@mbklein](https://github.com/mbklein)
|
||||
|
@ -160,15 +160,17 @@ const debuglog = util.debuglog('sharp');
|
||||
* @param {number} [options.raw.channels] - integral number of channels, between 1 and 4.
|
||||
* @param {boolean} [options.raw.premultiplied] - specifies that the raw input has already been premultiplied, set to `true`
|
||||
* to avoid sharp premultiplying the image. (optional, default `false`)
|
||||
* @param {number} [options.raw.pageHeight] - The pixel height of each page/frame for animated images, must be an integral factor of `raw.height`.
|
||||
* @param {Object} [options.create] - describes a new image to be created.
|
||||
* @param {number} [options.create.width] - integral number of pixels wide.
|
||||
* @param {number} [options.create.height] - integral number of pixels high.
|
||||
* @param {number} [options.create.channels] - integral number of channels, either 3 (RGB) or 4 (RGBA).
|
||||
* @param {string|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
||||
* @param {number} [options.create.pageHeight] - The pixel height of each page/frame for animated images, must be an integral factor of `create.height`.
|
||||
* @param {Object} [options.create.noise] - describes a noise to be created.
|
||||
* @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported.
|
||||
* @param {number} [options.create.noise.mean] - mean of pixels in generated noise.
|
||||
* @param {number} [options.create.noise.sigma] - standard deviation of pixels in generated noise.
|
||||
* @param {number} [options.create.noise.mean=128] - Mean value of pixels in the generated noise.
|
||||
* @param {number} [options.create.noise.sigma=30] - Standard deviation of pixel values in the generated noise.
|
||||
* @param {Object} [options.text] - describes a new text image to be created.
|
||||
* @param {string} [options.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
|
||||
* @param {string} [options.text.font] - font name to render with.
|
||||
|
7
lib/index.d.ts
vendored
7
lib/index.d.ts
vendored
@ -1061,6 +1061,8 @@ declare namespace sharp {
|
||||
interface CreateRaw extends Raw {
|
||||
/** Specifies that the raw input has already been premultiplied, set to true to avoid sharp premultiplying the image. (optional, default false) */
|
||||
premultiplied?: boolean | undefined;
|
||||
/** The height of each page/frame for animated images, must be an integral factor of the overall image height. */
|
||||
pageHeight?: number | undefined;
|
||||
}
|
||||
|
||||
type CreateChannels = 3 | 4;
|
||||
@ -1076,6 +1078,9 @@ declare namespace sharp {
|
||||
background: Colour | Color;
|
||||
/** Describes a noise to be created. */
|
||||
noise?: Noise | undefined;
|
||||
/** The height of each page/frame for animated images, must be an integral factor of the overall image height. */
|
||||
pageHeight?: number | undefined;
|
||||
|
||||
}
|
||||
|
||||
interface CreateText {
|
||||
@ -1549,7 +1554,7 @@ declare namespace sharp {
|
||||
|
||||
interface Noise {
|
||||
/** type of generated noise, currently only gaussian is supported. */
|
||||
type?: 'gaussian' | undefined;
|
||||
type: 'gaussian';
|
||||
/** mean of pixels in generated noise. */
|
||||
mean?: number | undefined;
|
||||
/** standard deviation of pixels in generated noise. */
|
||||
|
42
lib/input.js
42
lib/input.js
@ -185,8 +185,6 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
inputDescriptor.rawWidth = inputOptions.raw.width;
|
||||
inputDescriptor.rawHeight = inputOptions.raw.height;
|
||||
inputDescriptor.rawChannels = inputOptions.raw.channels;
|
||||
inputDescriptor.rawPremultiplied = !!inputOptions.raw.premultiplied;
|
||||
|
||||
switch (input.constructor) {
|
||||
case Uint8Array:
|
||||
case Uint8ClampedArray:
|
||||
@ -220,6 +218,25 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
} else {
|
||||
throw new Error('Expected width, height and channels for raw pixel input');
|
||||
}
|
||||
inputDescriptor.rawPremultiplied = false;
|
||||
if (is.defined(inputOptions.raw.premultiplied)) {
|
||||
if (is.bool(inputOptions.raw.premultiplied)) {
|
||||
inputDescriptor.rawPremultiplied = inputOptions.raw.premultiplied;
|
||||
} else {
|
||||
throw is.invalidParameterError('raw.premultiplied', 'boolean', inputOptions.raw.premultiplied);
|
||||
}
|
||||
}
|
||||
inputDescriptor.rawPageHeight = 0;
|
||||
if (is.defined(inputOptions.raw.pageHeight)) {
|
||||
if (is.integer(inputOptions.raw.pageHeight) && inputOptions.raw.pageHeight > 0 && inputOptions.raw.pageHeight <= inputOptions.raw.height) {
|
||||
if (inputOptions.raw.height % inputOptions.raw.pageHeight !== 0) {
|
||||
throw new Error(`Expected raw.height ${inputOptions.raw.height} to be a multiple of raw.pageHeight ${inputOptions.raw.pageHeight}`);
|
||||
}
|
||||
inputDescriptor.rawPageHeight = inputOptions.raw.pageHeight;
|
||||
} else {
|
||||
throw is.invalidParameterError('raw.pageHeight', 'positive integer', inputOptions.raw.pageHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Multi-page input (GIF, TIFF, PDF)
|
||||
if (is.defined(inputOptions.animated)) {
|
||||
@ -316,28 +333,45 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
inputDescriptor.createWidth = inputOptions.create.width;
|
||||
inputDescriptor.createHeight = inputOptions.create.height;
|
||||
inputDescriptor.createChannels = inputOptions.create.channels;
|
||||
inputDescriptor.createPageHeight = 0;
|
||||
if (is.defined(inputOptions.create.pageHeight)) {
|
||||
if (is.integer(inputOptions.create.pageHeight) && inputOptions.create.pageHeight > 0 && inputOptions.create.pageHeight <= inputOptions.create.height) {
|
||||
if (inputOptions.create.height % inputOptions.create.pageHeight !== 0) {
|
||||
throw new Error(`Expected create.height ${inputOptions.create.height} to be a multiple of create.pageHeight ${inputOptions.create.pageHeight}`);
|
||||
}
|
||||
inputDescriptor.createPageHeight = inputOptions.create.pageHeight;
|
||||
} else {
|
||||
throw is.invalidParameterError('create.pageHeight', 'positive integer', inputOptions.create.pageHeight);
|
||||
}
|
||||
}
|
||||
// Noise
|
||||
if (is.defined(inputOptions.create.noise)) {
|
||||
if (!is.object(inputOptions.create.noise)) {
|
||||
throw new Error('Expected noise to be an object');
|
||||
}
|
||||
if (!is.inArray(inputOptions.create.noise.type, ['gaussian'])) {
|
||||
if (inputOptions.create.noise.type !== 'gaussian') {
|
||||
throw new Error('Only gaussian noise is supported at the moment');
|
||||
}
|
||||
inputDescriptor.createNoiseType = inputOptions.create.noise.type;
|
||||
if (!is.inRange(inputOptions.create.channels, 1, 4)) {
|
||||
throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels);
|
||||
}
|
||||
inputDescriptor.createNoiseType = inputOptions.create.noise.type;
|
||||
inputDescriptor.createNoiseMean = 128;
|
||||
if (is.defined(inputOptions.create.noise.mean)) {
|
||||
if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) {
|
||||
inputDescriptor.createNoiseMean = inputOptions.create.noise.mean;
|
||||
} else {
|
||||
throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean);
|
||||
}
|
||||
}
|
||||
inputDescriptor.createNoiseSigma = 30;
|
||||
if (is.defined(inputOptions.create.noise.sigma)) {
|
||||
if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) {
|
||||
inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma;
|
||||
} else {
|
||||
throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma);
|
||||
}
|
||||
}
|
||||
} else if (is.defined(inputOptions.create.background)) {
|
||||
if (!is.inRange(inputOptions.create.channels, 3, 4)) {
|
||||
throw is.invalidParameterError('create.channels', 'number between 3 and 4', inputOptions.create.channels);
|
||||
|
@ -179,12 +179,12 @@
|
||||
"icc": "^3.0.0",
|
||||
"jsdoc-to-markdown": "^9.1.1",
|
||||
"license-checker": "^25.0.1",
|
||||
"mocha": "^11.6.0",
|
||||
"node-addon-api": "^8.3.1",
|
||||
"mocha": "^11.7.0",
|
||||
"node-addon-api": "^8.4.0",
|
||||
"node-gyp": "^11.2.0",
|
||||
"nyc": "^17.1.0",
|
||||
"semistandard": "^17.0.0",
|
||||
"tar-fs": "^3.0.9",
|
||||
"tar-fs": "^3.0.10",
|
||||
"tsd": "^0.32.0"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
|
@ -93,6 +93,7 @@ namespace sharp {
|
||||
descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
|
||||
descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
|
||||
descriptor->rawPremultiplied = AttrAsBool(input, "rawPremultiplied");
|
||||
descriptor->rawPageHeight = AttrAsUint32(input, "rawPageHeight");
|
||||
}
|
||||
// Multi-page input (GIF, TIFF, PDF)
|
||||
if (HasAttr(input, "pages")) {
|
||||
@ -129,6 +130,7 @@ namespace sharp {
|
||||
descriptor->createChannels = AttrAsUint32(input, "createChannels");
|
||||
descriptor->createWidth = AttrAsUint32(input, "createWidth");
|
||||
descriptor->createHeight = AttrAsUint32(input, "createHeight");
|
||||
descriptor->createPageHeight = AttrAsUint32(input, "createPageHeight");
|
||||
if (HasAttr(input, "createNoiseType")) {
|
||||
descriptor->createNoiseType = AttrAsStr(input, "createNoiseType");
|
||||
descriptor->createNoiseMean = AttrAsDouble(input, "createNoiseMean");
|
||||
@ -453,6 +455,10 @@ namespace sharp {
|
||||
} else {
|
||||
image.get_image()->Type = is8bit ? VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_RGB16;
|
||||
}
|
||||
if (descriptor->rawPageHeight > 0) {
|
||||
image.set(VIPS_META_PAGE_HEIGHT, descriptor->rawPageHeight);
|
||||
image.set(VIPS_META_N_PAGES, static_cast<int>(descriptor->rawHeight / descriptor->rawPageHeight));
|
||||
}
|
||||
if (descriptor->rawPremultiplied) {
|
||||
image = image.unpremultiply();
|
||||
}
|
||||
@ -502,6 +508,10 @@ namespace sharp {
|
||||
channels < 3 ? VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB))
|
||||
.new_from_image(background);
|
||||
}
|
||||
if (descriptor->createPageHeight > 0) {
|
||||
image.set(VIPS_META_PAGE_HEIGHT, descriptor->createPageHeight);
|
||||
image.set(VIPS_META_N_PAGES, static_cast<int>(descriptor->createHeight / descriptor->createPageHeight));
|
||||
}
|
||||
image = image.cast(VIPS_FORMAT_UCHAR);
|
||||
imageType = ImageType::RAW;
|
||||
} else if (descriptor->textValue.length() > 0) {
|
||||
|
@ -48,11 +48,13 @@ namespace sharp {
|
||||
int rawWidth;
|
||||
int rawHeight;
|
||||
bool rawPremultiplied;
|
||||
int rawPageHeight;
|
||||
int pages;
|
||||
int page;
|
||||
int createChannels;
|
||||
int createWidth;
|
||||
int createHeight;
|
||||
int createPageHeight;
|
||||
std::vector<double> createBackground;
|
||||
std::string createNoiseType;
|
||||
double createNoiseMean;
|
||||
@ -98,11 +100,13 @@ namespace sharp {
|
||||
rawWidth(0),
|
||||
rawHeight(0),
|
||||
rawPremultiplied(false),
|
||||
rawPageHeight(0),
|
||||
pages(1),
|
||||
page(0),
|
||||
createChannels(0),
|
||||
createWidth(0),
|
||||
createHeight(0),
|
||||
createPageHeight(0),
|
||||
createBackground{ 0.0, 0.0, 0.0, 255.0 },
|
||||
createNoiseMean(0.0),
|
||||
createNoiseSigma(0.0),
|
||||
|
@ -418,6 +418,7 @@ sharp({
|
||||
channels: 4,
|
||||
height: 25000,
|
||||
width: 25000,
|
||||
pageHeight: 1000,
|
||||
},
|
||||
limitInputPixels: false,
|
||||
})
|
||||
@ -734,6 +735,13 @@ sharp({ svg: { stylesheet: 'test' }});
|
||||
sharp({ svg: { highBitdepth: true }});
|
||||
sharp({ svg: { highBitdepth: false }});
|
||||
|
||||
// Raw input options
|
||||
const raw: sharp.Raw = { width: 1, height: 1, channels: 3 };
|
||||
sharp({ raw });
|
||||
sharp({ raw: { ...raw, premultiplied: true } });
|
||||
sharp({ raw: { ...raw, premultiplied: false } });
|
||||
sharp({ raw: { ...raw, pageHeight: 1 } });
|
||||
|
||||
sharp({ autoOrient: true });
|
||||
sharp({ autoOrient: false });
|
||||
sharp().autoOrient();
|
||||
|
@ -173,6 +173,26 @@ describe('Gaussian noise', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('animated noise', async () => {
|
||||
const gif = await sharp({
|
||||
create: {
|
||||
width: 16,
|
||||
height: 64,
|
||||
pageHeight: 16,
|
||||
channels: 3,
|
||||
noise: { type: 'gaussian' }
|
||||
}
|
||||
})
|
||||
.gif()
|
||||
.toBuffer();
|
||||
|
||||
const { width, height, pages, delay } = await sharp(gif).metadata();
|
||||
assert.strictEqual(width, 16);
|
||||
assert.strictEqual(height, 16);
|
||||
assert.strictEqual(pages, 4);
|
||||
assert.strictEqual(delay.length, 4);
|
||||
});
|
||||
|
||||
it('no create object properties specified', function () {
|
||||
assert.throws(function () {
|
||||
sharp({
|
||||
@ -259,4 +279,29 @@ describe('Gaussian noise', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid pageHeight', () => {
|
||||
const create = {
|
||||
width: 8,
|
||||
height: 8,
|
||||
channels: 4,
|
||||
noise: { type: 'gaussian' }
|
||||
};
|
||||
assert.throws(
|
||||
() => sharp({ create: { ...create, pageHeight: 'zoinks' } }),
|
||||
/Expected positive integer for create\.pageHeight but received zoinks of type string/
|
||||
);
|
||||
assert.throws(
|
||||
() => sharp({ create: { ...create, pageHeight: -1 } }),
|
||||
/Expected positive integer for create\.pageHeight but received -1 of type number/
|
||||
);
|
||||
assert.throws(
|
||||
() => sharp({ create: { ...create, pageHeight: 9 } }),
|
||||
/Expected positive integer for create\.pageHeight but received 9 of type number/
|
||||
);
|
||||
assert.throws(
|
||||
() => sharp({ create: { ...create, pageHeight: 3 } }),
|
||||
/Expected create\.height 8 to be a multiple of create\.pageHeight 3/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -55,6 +55,35 @@ describe('Raw pixel data', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid premultiplied', () => {
|
||||
assert.throws(
|
||||
() => sharp({ raw: { width: 1, height: 1, channels: 4, premultiplied: 'zoinks' } }),
|
||||
/Expected boolean for raw\.premultiplied but received zoinks of type string/
|
||||
);
|
||||
});
|
||||
|
||||
it('Invalid pageHeight', () => {
|
||||
const width = 8;
|
||||
const height = 8;
|
||||
const channels = 4;
|
||||
assert.throws(
|
||||
() => sharp({ raw: { width, height, channels, pageHeight: 'zoinks' } }),
|
||||
/Expected positive integer for raw\.pageHeight but received zoinks of type string/
|
||||
);
|
||||
assert.throws(
|
||||
() => sharp({ raw: { width, height, channels, pageHeight: -1 } }),
|
||||
/Expected positive integer for raw\.pageHeight but received -1 of type number/
|
||||
);
|
||||
assert.throws(
|
||||
() => sharp({ raw: { width, height, channels, pageHeight: 9 } }),
|
||||
/Expected positive integer for raw\.pageHeight but received 9 of type number/
|
||||
);
|
||||
assert.throws(
|
||||
() => sharp({ raw: { width, height, channels, pageHeight: 3 } }),
|
||||
/Expected raw\.height 8 to be a multiple of raw\.pageHeight 3/
|
||||
);
|
||||
});
|
||||
|
||||
it('RGB', function (done) {
|
||||
// Convert to raw pixel data
|
||||
sharp(fixtures.inputJpg)
|
||||
@ -285,6 +314,23 @@ describe('Raw pixel data', function () {
|
||||
}
|
||||
});
|
||||
|
||||
it('Animated', async () => {
|
||||
const gif = await sharp(
|
||||
Buffer.alloc(8),
|
||||
{ raw: { width: 1, height: 2, channels: 4, pageHeight: 1 }, animated: true }
|
||||
)
|
||||
.gif({ keepDuplicateFrames: true })
|
||||
.toBuffer();
|
||||
|
||||
console.log(await sharp(gif).metadata());
|
||||
|
||||
const { width, height, pages, delay } = await sharp(gif).metadata();
|
||||
assert.strictEqual(width, 1);
|
||||
assert.strictEqual(height, 1);
|
||||
assert.strictEqual(pages, 2);
|
||||
assert.strictEqual(delay.length, 2);
|
||||
});
|
||||
|
||||
describe('16-bit roundtrip', () => {
|
||||
it('grey', async () => {
|
||||
const grey = 42000;
|
||||
|
Loading…
x
Reference in New Issue
Block a user