Ensure tests pass with latest libvips master branch

Expose forthcoming HEIF features where available
This commit is contained in:
Lovell Fuller 2021-01-13 16:47:49 +00:00
parent 138e60adb3
commit 8d49b7dde1
12 changed files with 50 additions and 9 deletions

View File

@ -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

View File

@ -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',

View File

@ -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 '';
} }

View File

@ -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);
} }

View File

@ -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()));

View File

@ -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;

View File

@ -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")) {

View File

@ -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),

View File

@ -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',

View File

@ -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);
}); });
}); });

View File

@ -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' });
});
});
}); });

View File

@ -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();
}); });
}); });