mirror of
https://github.com/lovell/sharp.git
synced 2025-07-13 12:20:13 +02:00
Expose libvips centre option, mimics *magick +0.5px convention
This commit is contained in:
parent
deb978bf57
commit
9911863441
@ -68,6 +68,7 @@ Use these JPEG options for output image.
|
||||
- `trellisQuantisation` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** apply trellis quantisation, requires mozjpeg (optional, default `false`)
|
||||
- `overshootDeringing` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** apply overshoot deringing, requires mozjpeg (optional, default `false`)
|
||||
- `optimiseScans` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** optimise progressive scans, forces progressive, requires mozjpeg (optional, default `false`)
|
||||
- `optimizeScans` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** alternative spelling of optimiseScans (optional, default `false`)
|
||||
|
||||
|
||||
- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options
|
||||
|
@ -27,6 +27,8 @@ Possible enlargement interpolators are:
|
||||
- `options` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]**
|
||||
- `options.kernel` **\[[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)]** the kernel to use for image reduction. (optional, default `'lanczos3'`)
|
||||
- `options.interpolator` **\[[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)]** the interpolator to use for image enlargement. (optional, default `'bicubic'`)
|
||||
- `options.centreSampling` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** use \*magick centre sampling convention instead of corner sampling. (optional, default `false`)
|
||||
- `options.centerSampling` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** alternative spelling of centreSampling. (optional, default `false`)
|
||||
|
||||
**Examples**
|
||||
|
||||
|
@ -15,6 +15,9 @@ Requires libvips v8.4.2.
|
||||
Access to these is now via output format functions, for example `quality(n)`
|
||||
is now `jpeg({quality: n})` and/or `webp({quality: n})`.
|
||||
|
||||
* Expose libvips' "centre" resize option to mimic \*magick's +0.5px convention.
|
||||
[#568](https://github.com/lovell/sharp/issues/568)
|
||||
|
||||
* Ensure support for embedded base64 PNG and JPEG images within an SVG.
|
||||
[#601](https://github.com/lovell/sharp/issues/601)
|
||||
[@dynamite-ready](https://github.com/dynamite-ready)
|
||||
|
@ -102,6 +102,7 @@ const Sharp = function (input, options) {
|
||||
withoutEnlargement: false,
|
||||
kernel: 'lanczos3',
|
||||
interpolator: 'bicubic',
|
||||
centreSampling: false,
|
||||
// operations
|
||||
background: [0, 0, 0, 255],
|
||||
flatten: false,
|
||||
|
@ -115,14 +115,14 @@ const jpeg = function jpeg (options) {
|
||||
throw new Error('Invalid chromaSubsampling (4:2:0, 4:4:4) ' + options.chromaSubsampling);
|
||||
}
|
||||
}
|
||||
options.trellisQuantisation = options.trellisQuantisation || options.trellisQuantization;
|
||||
options.trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation;
|
||||
if (is.defined(options.trellisQuantisation)) {
|
||||
this._setBooleanOption('jpegTrellisQuantisation', options.trellisQuantisation);
|
||||
}
|
||||
if (is.defined(options.overshootDeringing)) {
|
||||
this._setBooleanOption('jpegOvershootDeringing', options.overshootDeringing);
|
||||
}
|
||||
options.optimiseScans = is.bool(options.optimiseScans) ? options.optimiseScans : options.optimizeScans;
|
||||
options.optimiseScans = is.bool(options.optimizeScans) ? options.optimizeScans : options.optimiseScans;
|
||||
if (is.defined(options.optimiseScans)) {
|
||||
this._setBooleanOption('jpegOptimiseScans', options.optimiseScans);
|
||||
if (options.optimiseScans) {
|
||||
|
@ -94,6 +94,8 @@ const interpolator = {
|
||||
* @param {Object} [options]
|
||||
* @param {String} [options.kernel='lanczos3'] - the kernel to use for image reduction.
|
||||
* @param {String} [options.interpolator='bicubic'] - the interpolator to use for image enlargement.
|
||||
* @param {Boolean} [options.centreSampling=false] - use *magick centre sampling convention instead of corner sampling.
|
||||
* @param {Boolean} [options.centerSampling=false] - alternative spelling of centreSampling.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
@ -133,6 +135,11 @@ const resize = function resize (width, height, options) {
|
||||
throw new Error('Invalid interpolator ' + options.interpolator);
|
||||
}
|
||||
}
|
||||
// Centre sampling
|
||||
options.centreSampling = is.bool(options.centerSampling) ? options.centerSampling : options.centreSampling;
|
||||
if (is.defined(options.centreSampling)) {
|
||||
this._setBooleanOption('centreSampling', options.centreSampling);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
@ -388,10 +388,16 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
throw vips::VError("Unknown kernel");
|
||||
}
|
||||
if (yresidual < 1.0) {
|
||||
image = image.reducev(1.0 / yresidual, VImage::option()->set("kernel", kernel));
|
||||
image = image.reducev(1.0 / yresidual, VImage::option()
|
||||
->set("kernel", kernel)
|
||||
->set("centre", baton->centreSampling)
|
||||
);
|
||||
}
|
||||
if (xresidual < 1.0) {
|
||||
image = image.reduceh(1.0 / xresidual, VImage::option()->set("kernel", kernel));
|
||||
image = image.reduceh(1.0 / xresidual, VImage::option()
|
||||
->set("kernel", kernel)
|
||||
->set("centre", baton->centreSampling)
|
||||
);
|
||||
}
|
||||
}
|
||||
// Perform affine enlargement
|
||||
@ -1063,6 +1069,7 @@ NAN_METHOD(pipeline) {
|
||||
baton->crop = AttrTo<int32_t>(options, "crop");
|
||||
baton->kernel = AttrAsStr(options, "kernel");
|
||||
baton->interpolator = AttrAsStr(options, "interpolator");
|
||||
baton->centreSampling = AttrTo<bool>(options, "centreSampling");
|
||||
// Join Channel Options
|
||||
if(HasAttr(options, "joinChannelIn")) {
|
||||
v8::Local<v8::Object> joinChannelObject = Nan::Get(options, Nan::New("joinChannelIn").ToLocalChecked())
|
||||
|
@ -50,6 +50,7 @@ struct PipelineBaton {
|
||||
int cropCalcTop;
|
||||
std::string kernel;
|
||||
std::string interpolator;
|
||||
bool centreSampling;
|
||||
double background[4];
|
||||
bool flatten;
|
||||
bool negate;
|
||||
@ -119,6 +120,7 @@ struct PipelineBaton {
|
||||
crop(0),
|
||||
cropCalcLeft(-1),
|
||||
cropCalcTop(-1),
|
||||
centreSampling(false),
|
||||
flatten(false),
|
||||
negate(false),
|
||||
blurSigma(0.0),
|
||||
|
@ -51,137 +51,110 @@ describe('Resize dimensions', function () {
|
||||
});
|
||||
|
||||
it('Upscale', function (done) {
|
||||
sharp(fixtures.inputJpg).resize(3000).toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(3000, info.width);
|
||||
assert.strictEqual(2450, info.height);
|
||||
done();
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(3000)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(3000, info.width);
|
||||
assert.strictEqual(2450, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid width - NaN', function () {
|
||||
assert.throws(function () {
|
||||
sharp().resize('spoons', 240);
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid width - NaN', function (done) {
|
||||
let isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).resize('spoons', 240);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Invalid height - NaN', function (done) {
|
||||
let isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).resize(320, 'spoons');
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Invalid width - float', function (done) {
|
||||
let isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).resize(1.5, 240);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Invalid height - float', function (done) {
|
||||
let isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).resize(320, 1.5);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Invalid width - too large', function (done) {
|
||||
let isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).resize(0x4000, 240);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Invalid height - too large', function (done) {
|
||||
let isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).resize(320, 0x4000);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
if (sharp.format.webp.output.buffer) {
|
||||
it('WebP shrink-on-load rounds to zero, ensure recalculation is correct', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(1080, 607)
|
||||
.webp()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('webp', info.format);
|
||||
assert.strictEqual(1080, info.width);
|
||||
assert.strictEqual(607, info.height);
|
||||
sharp(data)
|
||||
.resize(233, 131)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('webp', info.format);
|
||||
assert.strictEqual(233, info.width);
|
||||
assert.strictEqual(131, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('Invalid height - NaN', function () {
|
||||
assert.throws(function () {
|
||||
sharp().resize(320, 'spoons');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (sharp.format.tiff.input.file) {
|
||||
it('TIFF embed known to cause rounding errors', function (done) {
|
||||
sharp(fixtures.inputTiff)
|
||||
.resize(240, 320)
|
||||
.embed()
|
||||
.jpeg()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(240, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
it('Invalid width - float', function () {
|
||||
assert.throws(function () {
|
||||
sharp().resize(1.5, 240);
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF known to cause rounding errors', function (done) {
|
||||
sharp(fixtures.inputTiff)
|
||||
.resize(240, 320)
|
||||
.jpeg()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(240, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
it('Invalid height - float', function () {
|
||||
assert.throws(function () {
|
||||
sharp().resize(320, 1.5);
|
||||
});
|
||||
});
|
||||
|
||||
it('Max width or height considering ratio (portrait)', function (done) {
|
||||
sharp(fixtures.inputTiff).resize(320, 320).max().jpeg().toBuffer(function (err, data, info) {
|
||||
it('Invalid width - too large', function () {
|
||||
assert.throws(function () {
|
||||
sharp().resize(0x4000, 240);
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid height - too large', function () {
|
||||
assert.throws(function () {
|
||||
sharp().resize(320, 0x4000);
|
||||
});
|
||||
});
|
||||
|
||||
it('WebP shrink-on-load rounds to zero, ensure recalculation is correct', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(1080, 607)
|
||||
.webp()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('webp', info.format);
|
||||
assert.strictEqual(1080, info.width);
|
||||
assert.strictEqual(607, info.height);
|
||||
sharp(data)
|
||||
.resize(233, 131)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('webp', info.format);
|
||||
assert.strictEqual(233, info.width);
|
||||
assert.strictEqual(131, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF embed known to cause rounding errors', function (done) {
|
||||
sharp(fixtures.inputTiff)
|
||||
.resize(240, 320)
|
||||
.embed()
|
||||
.jpeg()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(240, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF known to cause rounding errors', function (done) {
|
||||
sharp(fixtures.inputTiff)
|
||||
.resize(240, 320)
|
||||
.jpeg()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(240, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Max width or height considering ratio (portrait)', function (done) {
|
||||
sharp(fixtures.inputTiff)
|
||||
.resize(320, 320)
|
||||
.max()
|
||||
.jpeg()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
@ -189,10 +162,14 @@ describe('Resize dimensions', function () {
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Min width or height considering ratio (portrait)', function (done) {
|
||||
sharp(fixtures.inputTiff).resize(320, 320).min().jpeg().toBuffer(function (err, data, info) {
|
||||
it('Min width or height considering ratio (portrait)', function (done) {
|
||||
sharp(fixtures.inputTiff)
|
||||
.resize(320, 320)
|
||||
.min()
|
||||
.jpeg()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
@ -200,51 +177,62 @@ describe('Resize dimensions', function () {
|
||||
assert.strictEqual(422, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('Max width or height considering ratio (landscape)', function (done) {
|
||||
sharp(fixtures.inputJpg).resize(320, 320).max().toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(261, info.height);
|
||||
done();
|
||||
});
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 320)
|
||||
.max()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(261, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Provide only one dimension with max, should default to crop', function (done) {
|
||||
sharp(fixtures.inputJpg).resize(320).max().toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(261, info.height);
|
||||
done();
|
||||
});
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320)
|
||||
.max()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(261, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Min width or height considering ratio (landscape)', function (done) {
|
||||
sharp(fixtures.inputJpg).resize(320, 320).min().toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(392, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 320)
|
||||
.min()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(392, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Provide only one dimension with min, should default to crop', function (done) {
|
||||
sharp(fixtures.inputJpg).resize(320).min().toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(261, info.height);
|
||||
done();
|
||||
});
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320)
|
||||
.min()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(261, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Do not enlarge when input width is already less than output width', function (done) {
|
||||
@ -387,4 +375,31 @@ describe('Resize dimensions', function () {
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Centre vs corner convention return different results', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(32, 24, { centreSampling: false })
|
||||
.greyscale()
|
||||
.raw()
|
||||
.toBuffer(function (err, cornerData) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(768, cornerData.length);
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(32, 24, { centerSampling: true })
|
||||
.greyscale()
|
||||
.raw()
|
||||
.toBuffer(function (err, centreData) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(768, centreData.length);
|
||||
assert.notStrictEqual(0, cornerData.compare(centreData));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid centreSampling option', function () {
|
||||
assert.throws(function () {
|
||||
sharp().resize(32, 24, { centreSampling: 1 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user