mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Ensure tests pass with latest libvips master branch
Expose forthcoming HEIF features where available
This commit is contained in:
parent
138e60adb3
commit
8d49b7dde1
@ -327,6 +327,7 @@ most web browsers do not display these properly.
|
|||||||
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
|
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
|
||||||
- `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
|
- `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
|
||||||
- `options.speed` **[boolean][7]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
|
- `options.speed` **[boolean][7]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
|
||||||
|
- `options.chromaSubsampling` **[string][2]** set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0 (optional, default `'4:2:0'`)
|
||||||
|
|
||||||
|
|
||||||
- Throws **[Error][4]** Invalid options
|
- Throws **[Error][4]** Invalid options
|
||||||
@ -351,6 +352,7 @@ globally-installed libvips compiled with support for libheif, libde265 and x265.
|
|||||||
- `options.compression` **[boolean][7]** compression format: av1, hevc (optional, default `'av1'`)
|
- `options.compression` **[boolean][7]** compression format: av1, hevc (optional, default `'av1'`)
|
||||||
- `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
|
- `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
|
||||||
- `options.speed` **[boolean][7]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
|
- `options.speed` **[boolean][7]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
|
||||||
|
- `options.chromaSubsampling` **[string][2]** set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0 (optional, default `'4:2:0'`)
|
||||||
|
|
||||||
|
|
||||||
- Throws **[Error][4]** Invalid options
|
- Throws **[Error][4]** Invalid options
|
||||||
|
@ -251,6 +251,7 @@ const Sharp = function (input, options) {
|
|||||||
heifLossless: false,
|
heifLossless: false,
|
||||||
heifCompression: 'av1',
|
heifCompression: 'av1',
|
||||||
heifSpeed: 5,
|
heifSpeed: 5,
|
||||||
|
heifChromaSubsampling: '4:2:0',
|
||||||
tileSize: 256,
|
tileSize: 256,
|
||||||
tileOverlap: 0,
|
tileOverlap: 0,
|
||||||
tileContainer: 'fs',
|
tileContainer: 'fs',
|
||||||
|
@ -39,8 +39,9 @@ const cachePath = function () {
|
|||||||
|
|
||||||
const globalLibvipsVersion = function () {
|
const globalLibvipsVersion = function () {
|
||||||
if (process.platform !== 'win32') {
|
if (process.platform !== 'win32') {
|
||||||
const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout || '';
|
const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout;
|
||||||
return globalLibvipsVersion.trim();
|
/* istanbul ignore next */
|
||||||
|
return (globalLibvipsVersion || '').trim();
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
@ -583,6 +583,7 @@ function tiff (options) {
|
|||||||
* @param {number} [options.quality=50] - quality, integer 1-100
|
* @param {number} [options.quality=50] - quality, integer 1-100
|
||||||
* @param {boolean} [options.lossless=false] - use lossless compression
|
* @param {boolean} [options.lossless=false] - use lossless compression
|
||||||
* @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
|
* @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
|
||||||
|
* @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid options
|
* @throws {Error} Invalid options
|
||||||
*/
|
*/
|
||||||
@ -603,6 +604,7 @@ function avif (options) {
|
|||||||
* @param {boolean} [options.compression='av1'] - compression format: av1, hevc
|
* @param {boolean} [options.compression='av1'] - compression format: av1, hevc
|
||||||
* @param {boolean} [options.lossless=false] - use lossless compression
|
* @param {boolean} [options.lossless=false] - use lossless compression
|
||||||
* @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
|
* @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
|
||||||
|
* @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid options
|
* @throws {Error} Invalid options
|
||||||
*/
|
*/
|
||||||
@ -636,6 +638,13 @@ function heif (options) {
|
|||||||
throw is.invalidParameterError('speed', 'integer between 0 and 8', options.speed);
|
throw is.invalidParameterError('speed', 'integer between 0 and 8', options.speed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (is.defined(options.chromaSubsampling)) {
|
||||||
|
if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
|
||||||
|
this.options.heifChromaSubsampling = options.chromaSubsampling;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this._updateFormatOut('heif', options);
|
return this._updateFormatOut('heif', options);
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,9 @@ class MetadataWorker : public Napi::AsyncWorker {
|
|||||||
if (image.get_typeof("heif-primary") == G_TYPE_INT) {
|
if (image.get_typeof("heif-primary") == G_TYPE_INT) {
|
||||||
baton->pagePrimary = image.get_int("heif-primary");
|
baton->pagePrimary = image.get_int("heif-primary");
|
||||||
}
|
}
|
||||||
|
if (image.get_typeof("heif-compression") == VIPS_TYPE_REF_STRING) {
|
||||||
|
baton->compression = image.get_string("heif-compression");
|
||||||
|
}
|
||||||
if (image.get_typeof("openslide.level-count") == VIPS_TYPE_REF_STRING) {
|
if (image.get_typeof("openslide.level-count") == VIPS_TYPE_REF_STRING) {
|
||||||
int const levels = std::stoi(image.get_string("openslide.level-count"));
|
int const levels = std::stoi(image.get_string("openslide.level-count"));
|
||||||
for (int l = 0; l < levels; l++) {
|
for (int l = 0; l < levels; l++) {
|
||||||
@ -186,6 +189,9 @@ class MetadataWorker : public Napi::AsyncWorker {
|
|||||||
if (baton->pagePrimary > -1) {
|
if (baton->pagePrimary > -1) {
|
||||||
info.Set("pagePrimary", baton->pagePrimary);
|
info.Set("pagePrimary", baton->pagePrimary);
|
||||||
}
|
}
|
||||||
|
if (!baton->compression.empty()) {
|
||||||
|
info.Set("compression", baton->compression);
|
||||||
|
}
|
||||||
if (!baton->levels.empty()) {
|
if (!baton->levels.empty()) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size()));
|
Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size()));
|
||||||
|
@ -39,6 +39,7 @@ struct MetadataBaton {
|
|||||||
int loop;
|
int loop;
|
||||||
std::vector<int> delay;
|
std::vector<int> delay;
|
||||||
int pagePrimary;
|
int pagePrimary;
|
||||||
|
std::string compression;
|
||||||
std::vector<std::pair<int, int>> levels;
|
std::vector<std::pair<int, int>> levels;
|
||||||
bool hasProfile;
|
bool hasProfile;
|
||||||
bool hasAlpha;
|
bool hasAlpha;
|
||||||
|
@ -838,6 +838,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("compression", baton->heifCompression)
|
->set("compression", baton->heifCompression)
|
||||||
->set("Q", baton->heifQuality)
|
->set("Q", baton->heifQuality)
|
||||||
->set("speed", baton->heifSpeed)
|
->set("speed", baton->heifSpeed)
|
||||||
|
#ifdef VIPS_TYPE_FOREIGN_SUBSAMPLE
|
||||||
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
||||||
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
||||||
|
#endif
|
||||||
->set("lossless", baton->heifLossless)));
|
->set("lossless", baton->heifLossless)));
|
||||||
baton->bufferOut = static_cast<char*>(area->data);
|
baton->bufferOut = static_cast<char*>(area->data);
|
||||||
baton->bufferOutLength = area->length;
|
baton->bufferOutLength = area->length;
|
||||||
@ -972,6 +976,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("Q", baton->heifQuality)
|
->set("Q", baton->heifQuality)
|
||||||
->set("compression", baton->heifCompression)
|
->set("compression", baton->heifCompression)
|
||||||
->set("speed", baton->heifSpeed)
|
->set("speed", baton->heifSpeed)
|
||||||
|
#ifdef VIPS_TYPE_FOREIGN_SUBSAMPLE
|
||||||
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
||||||
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
||||||
|
#endif
|
||||||
->set("lossless", baton->heifLossless));
|
->set("lossless", baton->heifLossless));
|
||||||
baton->formatOut = "heif";
|
baton->formatOut = "heif";
|
||||||
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
||||||
@ -1396,6 +1404,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
|
||||||
sharp::AttrAsStr(options, "heifCompression").data()));
|
sharp::AttrAsStr(options, "heifCompression").data()));
|
||||||
baton->heifSpeed = sharp::AttrAsUint32(options, "heifSpeed");
|
baton->heifSpeed = sharp::AttrAsUint32(options, "heifSpeed");
|
||||||
|
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
|
||||||
|
|
||||||
// Animated output
|
// Animated output
|
||||||
if (sharp::HasAttr(options, "pageHeight")) {
|
if (sharp::HasAttr(options, "pageHeight")) {
|
||||||
|
@ -162,6 +162,7 @@ struct PipelineBaton {
|
|||||||
int heifQuality;
|
int heifQuality;
|
||||||
VipsForeignHeifCompression heifCompression;
|
VipsForeignHeifCompression heifCompression;
|
||||||
int heifSpeed;
|
int heifSpeed;
|
||||||
|
std::string heifChromaSubsampling;
|
||||||
bool heifLossless;
|
bool heifLossless;
|
||||||
std::string err;
|
std::string err;
|
||||||
bool withMetadata;
|
bool withMetadata;
|
||||||
@ -282,6 +283,7 @@ struct PipelineBaton {
|
|||||||
heifQuality(50),
|
heifQuality(50),
|
||||||
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
|
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
|
||||||
heifSpeed(5),
|
heifSpeed(5),
|
||||||
|
heifChromaSubsampling("4:2:0"),
|
||||||
heifLossless(false),
|
heifLossless(false),
|
||||||
withMetadata(false),
|
withMetadata(false),
|
||||||
withMetadataOrientation(-1),
|
withMetadataOrientation(-1),
|
||||||
|
@ -18,7 +18,7 @@ describe('AVIF', () => {
|
|||||||
.toBuffer();
|
.toBuffer();
|
||||||
const metadata = await sharp(data)
|
const metadata = await sharp(data)
|
||||||
.metadata();
|
.metadata();
|
||||||
const { size, ...metadataWithoutSize } = metadata;
|
const { compression, size, ...metadataWithoutSize } = metadata;
|
||||||
assert.deepStrictEqual(metadataWithoutSize, {
|
assert.deepStrictEqual(metadataWithoutSize, {
|
||||||
channels: 3,
|
channels: 3,
|
||||||
depth: 'uchar',
|
depth: 'uchar',
|
||||||
@ -42,7 +42,7 @@ describe('AVIF', () => {
|
|||||||
.toBuffer();
|
.toBuffer();
|
||||||
const metadata = await sharp(data)
|
const metadata = await sharp(data)
|
||||||
.metadata();
|
.metadata();
|
||||||
const { size, ...metadataWithoutSize } = metadata;
|
const { compression, size, ...metadataWithoutSize } = metadata;
|
||||||
assert.deepStrictEqual(metadataWithoutSize, {
|
assert.deepStrictEqual(metadataWithoutSize, {
|
||||||
channels: 3,
|
channels: 3,
|
||||||
chromaSubsampling: '4:2:0',
|
chromaSubsampling: '4:2:0',
|
||||||
@ -65,7 +65,7 @@ describe('AVIF', () => {
|
|||||||
.toBuffer();
|
.toBuffer();
|
||||||
const metadata = await sharp(data)
|
const metadata = await sharp(data)
|
||||||
.metadata();
|
.metadata();
|
||||||
const { size, ...metadataWithoutSize } = metadata;
|
const { compression, size, ...metadataWithoutSize } = metadata;
|
||||||
assert.deepStrictEqual(metadataWithoutSize, {
|
assert.deepStrictEqual(metadataWithoutSize, {
|
||||||
channels: 3,
|
channels: 3,
|
||||||
depth: 'uchar',
|
depth: 'uchar',
|
||||||
|
@ -53,7 +53,7 @@ describe('failOnError', function () {
|
|||||||
|
|
||||||
it('returns errors to callback for truncated JPEG', function (done) {
|
it('returns errors to callback for truncated JPEG', function (done) {
|
||||||
sharp(fixtures.inputJpgTruncated).toBuffer(function (err, data, info) {
|
sharp(fixtures.inputJpgTruncated).toBuffer(function (err, data, info) {
|
||||||
assert.ok(err.message.includes('VipsJpeg: Premature end of JPEG file'), err);
|
assert.ok(err.message.includes('VipsJpeg: Premature end of'), err);
|
||||||
assert.strictEqual(data, undefined);
|
assert.strictEqual(data, undefined);
|
||||||
assert.strictEqual(info, undefined);
|
assert.strictEqual(info, undefined);
|
||||||
done();
|
done();
|
||||||
@ -76,7 +76,7 @@ describe('failOnError', function () {
|
|||||||
throw new Error('Expected rejection');
|
throw new Error('Expected rejection');
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
done(err.message.includes('VipsJpeg: Premature end of JPEG file') ? undefined : err);
|
done(err.message.includes('VipsJpeg: Premature end of') ? undefined : err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -65,4 +65,14 @@ describe('HEIF', () => {
|
|||||||
sharp().heif({ compression: 'fail' });
|
sharp().heif({ compression: 'fail' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('invalid chromaSubsampling should throw an error', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().heif({ chromaSubsampling: 'fail' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('valid chromaSubsampling does not throw an error', () => {
|
||||||
|
assert.doesNotThrow(() => {
|
||||||
|
sharp().heif({ chromaSubsampling: '4:4:4' });
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -667,7 +667,7 @@ describe('Image metadata', function () {
|
|||||||
sharp(fixtures.inputJpgWithCorruptHeader)
|
sharp(fixtures.inputJpgWithCorruptHeader)
|
||||||
.metadata(function (err) {
|
.metadata(function (err) {
|
||||||
assert.strictEqual(true, !!err);
|
assert.strictEqual(true, !!err);
|
||||||
assert.strictEqual(true, /Input file has corrupt header: VipsJpeg: Premature end of JPEG file/.test(err.message));
|
assert.ok(err.message.includes('Input file has corrupt header: VipsJpeg: Premature end of'), err);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -676,7 +676,7 @@ describe('Image metadata', function () {
|
|||||||
sharp(fs.readFileSync(fixtures.inputJpgWithCorruptHeader))
|
sharp(fs.readFileSync(fixtures.inputJpgWithCorruptHeader))
|
||||||
.metadata(function (err) {
|
.metadata(function (err) {
|
||||||
assert.strictEqual(true, !!err);
|
assert.strictEqual(true, !!err);
|
||||||
assert.strictEqual(true, /Input buffer has corrupt header: VipsJpeg: Premature end of JPEG file/.test(err.message));
|
assert.ok(err.message.includes('Input buffer has corrupt header: VipsJpeg: Premature end of'), err);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user