Allow withMetadata to set density #967

This commit is contained in:
Lovell Fuller 2021-04-17 13:46:54 +01:00
parent 8c0c01c702
commit 4237f5520f
9 changed files with 85 additions and 6 deletions

View File

@ -120,6 +120,7 @@ sRGB colour space and strip all metadata, including the removal of any ICC profi
- `options.orientation` **[number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag. - `options.orientation` **[number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag.
- `options.icc` **[string][2]?** filesystem path to output ICC profile, defaults to sRGB. - `options.icc` **[string][2]?** filesystem path to output ICC profile, defaults to sRGB.
- `options.exif` **[Object][6]<[Object][6]>** Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. (optional, default `{}`) - `options.exif` **[Object][6]<[Object][6]>** Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. (optional, default `{}`)
- `options.density` **[number][9]?** Number of pixels per inch (DPI).
### Examples ### Examples
@ -132,7 +133,7 @@ sharp('input.jpg')
```javascript ```javascript
// Set "IFD0-Copyright" in output EXIF metadata // Set "IFD0-Copyright" in output EXIF metadata
await sharp(input) const data = await sharp(input)
.withMetadata({ .withMetadata({
exif: { exif: {
IFD0: { IFD0: {
@ -141,6 +142,12 @@ await sharp(input)
} }
}) })
.toBuffer(); .toBuffer();
* @example
// Set output metadata to 96 DPI
const data = await sharp(input)
.withMetadata({ density: 96 })
.toBuffer();
``` ```
- Throws **[Error][4]** Invalid parameters - Throws **[Error][4]** Invalid parameters

View File

@ -6,6 +6,9 @@ Requires libvips v8.10.6
### v0.28.2 - TBD ### v0.28.2 - TBD
* Allow `withMetadata` to set `density`.
[#967](https://github.com/lovell/sharp/issues/967)
* Skip shrink-on-load where one dimension <4px. * Skip shrink-on-load where one dimension <4px.
[#2653](https://github.com/lovell/sharp/issues/2653) [#2653](https://github.com/lovell/sharp/issues/2653)

View File

@ -231,6 +231,7 @@ const Sharp = function (input, options) {
streamOut: false, streamOut: false,
withMetadata: false, withMetadata: false,
withMetadataOrientation: -1, withMetadataOrientation: -1,
withMetadataDensity: 0,
withMetadataIcc: '', withMetadataIcc: '',
withMetadataStrs: {}, withMetadataStrs: {},
resolveWithObject: false, resolveWithObject: false,

View File

@ -150,7 +150,7 @@ function toBuffer (options, callback) {
* *
* @example * @example
* // Set "IFD0-Copyright" in output EXIF metadata * // Set "IFD0-Copyright" in output EXIF metadata
* await sharp(input) * const data = await sharp(input)
* .withMetadata({ * .withMetadata({
* exif: { * exif: {
* IFD0: { * IFD0: {
@ -160,10 +160,17 @@ function toBuffer (options, callback) {
* }) * })
* .toBuffer(); * .toBuffer();
* *
* * @example
* // Set output metadata to 96 DPI
* const data = await sharp(input)
* .withMetadata({ density: 96 })
* .toBuffer();
*
* @param {Object} [options] * @param {Object} [options]
* @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag. * @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
* @param {string} [options.icc] filesystem path to output ICC profile, defaults to sRGB. * @param {string} [options.icc] filesystem path to output ICC profile, defaults to sRGB.
* @param {Object<Object>} [options.exif={}] Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. * @param {Object<Object>} [options.exif={}] Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
* @param {number} [options.density] Number of pixels per inch (DPI).
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@ -177,6 +184,13 @@ function withMetadata (options) {
throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation); throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation);
} }
} }
if (is.defined(options.density)) {
if (is.number(options.density) && options.density > 0) {
this.options.withMetadataDensity = options.density;
} else {
throw is.invalidParameterError('density', 'positive number', options.density);
}
}
if (is.defined(options.icc)) { if (is.defined(options.icc)) {
if (is.string(options.icc)) { if (is.string(options.icc)) {
this.options.withMetadataIcc = options.icc; this.options.withMetadataIcc = options.icc;

View File

@ -130,7 +130,7 @@
"async": "^3.2.0", "async": "^3.2.0",
"cc": "^3.0.1", "cc": "^3.0.1",
"decompress-zip": "^0.3.3", "decompress-zip": "^0.3.3",
"documentation": "^13.2.0", "documentation": "^13.2.1",
"exif-reader": "^1.0.3", "exif-reader": "^1.0.3",
"icc": "^2.0.0", "icc": "^2.0.0",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",

View File

@ -520,9 +520,8 @@ namespace sharp {
VImage SetDensity(VImage image, const double density) { VImage SetDensity(VImage image, const double density) {
const double pixelsPerMm = density / 25.4; const double pixelsPerMm = density / 25.4;
VImage copy = image.copy(); VImage copy = image.copy();
copy.set("Xres", pixelsPerMm); copy.get_image()->Xres = pixelsPerMm;
copy.set("Yres", pixelsPerMm); copy.get_image()->Yres = pixelsPerMm;
copy.set(VIPS_META_RESOLUTION_UNIT, "in");
return copy; return copy;
} }

View File

@ -722,6 +722,10 @@ class PipelineWorker : public Napi::AsyncWorker {
if (baton->withMetadata && baton->withMetadataOrientation != -1) { if (baton->withMetadata && baton->withMetadataOrientation != -1) {
image = sharp::SetExifOrientation(image, baton->withMetadataOrientation); image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
} }
// Override pixel density
if (baton->withMetadataDensity > 0) {
image = sharp::SetDensity(image, baton->withMetadataDensity);
}
// Metadata key/value pairs, e.g. EXIF // Metadata key/value pairs, e.g. EXIF
if (!baton->withMetadataStrs.empty()) { if (!baton->withMetadataStrs.empty()) {
image = image.copy(); image = image.copy();
@ -1385,6 +1389,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->fileOut = sharp::AttrAsStr(options, "fileOut"); baton->fileOut = sharp::AttrAsStr(options, "fileOut");
baton->withMetadata = sharp::AttrAsBool(options, "withMetadata"); baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation"); baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
baton->withMetadataIcc = sharp::AttrAsStr(options, "withMetadataIcc"); baton->withMetadataIcc = sharp::AttrAsStr(options, "withMetadataIcc");
Napi::Object mdStrs = options.Get("withMetadataStrs").As<Napi::Object>(); Napi::Object mdStrs = options.Get("withMetadataStrs").As<Napi::Object>();
Napi::Array mdStrKeys = mdStrs.GetPropertyNames(); Napi::Array mdStrKeys = mdStrs.GetPropertyNames();

View File

@ -168,6 +168,7 @@ struct PipelineBaton {
std::string err; std::string err;
bool withMetadata; bool withMetadata;
int withMetadataOrientation; int withMetadataOrientation;
double withMetadataDensity;
std::string withMetadataIcc; std::string withMetadataIcc;
std::unordered_map<std::string, std::string> withMetadataStrs; std::unordered_map<std::string, std::string> withMetadataStrs;
std::unique_ptr<double[]> convKernel; std::unique_ptr<double[]> convKernel;
@ -290,6 +291,7 @@ struct PipelineBaton {
heifLossless(false), heifLossless(false),
withMetadata(false), withMetadata(false),
withMetadataOrientation(-1), withMetadataOrientation(-1),
withMetadataDensity(0.0),
convKernelWidth(0), convKernelWidth(0),
convKernelHeight(0), convKernelHeight(0),
convKernelScale(0.0), convKernelScale(0.0),

View File

@ -623,6 +623,44 @@ describe('Image metadata', function () {
assert.strictEqual(parsedExif.exif.ExposureTime, 0.2); assert.strictEqual(parsedExif.exif.ExposureTime, 0.2);
}); });
it('Set density of JPEG', async () => {
const data = await sharp({
create: {
width: 8,
height: 8,
channels: 3,
background: 'red'
}
})
.withMetadata({
density: 300
})
.jpeg()
.toBuffer();
const { density } = await sharp(data).metadata();
assert.strictEqual(density, 300);
});
it('Set density of PNG', async () => {
const data = await sharp({
create: {
width: 8,
height: 8,
channels: 3,
background: 'red'
}
})
.withMetadata({
density: 96
})
.png()
.toBuffer();
const { density } = await sharp(data).metadata();
assert.strictEqual(density, 96);
});
it('chromaSubsampling 4:4:4:4 CMYK JPEG', function () { it('chromaSubsampling 4:4:4:4 CMYK JPEG', function () {
return sharp(fixtures.inputJpgWithCmykProfile) return sharp(fixtures.inputJpgWithCmykProfile)
.metadata() .metadata()
@ -736,6 +774,16 @@ describe('Image metadata', function () {
sharp().withMetadata({ orientation: 9 }); sharp().withMetadata({ orientation: 9 });
}); });
}); });
it('Non-numeric density', function () {
assert.throws(function () {
sharp().withMetadata({ density: '1' });
});
});
it('Negative density', function () {
assert.throws(function () {
sharp().withMetadata({ density: -1 });
});
});
it('Non string icc', function () { it('Non string icc', function () {
assert.throws(function () { assert.throws(function () {
sharp().withMetadata({ icc: true }); sharp().withMetadata({ icc: true });