diff --git a/docs/api.md b/docs/api.md
index 963561fe..a69ae6c4 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -48,6 +48,7 @@ Fast access to image metadata without decoding any compressed image data.
* `height`: Number of pixels high
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L522)
* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
+* `density`: Number of pixels per inch (DPI), if present
* `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* `orientation`: Number value of the EXIF Orientation header, if present
diff --git a/docs/changelog.md b/docs/changelog.md
index fa521173..8393616c 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -6,6 +6,10 @@
[#239](https://github.com/lovell/sharp/issues/239)
[@chrisriley](https://github.com/chrisriley)
+* Expose density metadata; set density of images from vector input.
+ [#338](https://github.com/lovell/sharp/issues/338)
+ [@lookfirst](https://github.com/lookfirst)
+
### v0.13 - "*mind*"
#### v0.13.1 - 27th February 2016
diff --git a/index.js b/index.js
index d959e1f3..4a906b4e 100644
--- a/index.js
+++ b/index.js
@@ -46,7 +46,7 @@ var Sharp = function(input, options) {
streamIn: false,
sequentialRead: false,
limitInputPixels: maximum.pixels,
- density: '72',
+ density: 72,
rawWidth: 0,
rawHeight: 0,
rawChannels: 0,
@@ -172,7 +172,7 @@ Sharp.prototype._inputOptions = function(options) {
// Density
if (isDefined(options.density)) {
if (isInteger(options.density) && inRange(options.density, 1, 2400)) {
- this.options.density = options.density.toString();
+ this.options.density = options.density;
} else {
throw new Error('Invalid density (1 to 2400) ' + options.density);
}
diff --git a/src/common.cc b/src/common.cc
index 92aaaefb..46872aa8 100644
--- a/src/common.cc
+++ b/src/common.cc
@@ -176,6 +176,30 @@ namespace sharp {
SetExifOrientation(image, 0);
}
+ /*
+ Does this image have a non-default density?
+ */
+ bool HasDensity(VImage image) {
+ return image.xres() > 1.0;
+ }
+
+ /*
+ Get pixels/mm resolution as pixels/inch density.
+ */
+ int GetDensity(VImage image) {
+ return static_cast(round(image.xres() * 25.4));
+ }
+
+ /*
+ Set pixels/mm resolution based on a pixels/inch density.
+ */
+ void SetDensity(VImage image, const int density) {
+ const double pixelsPerMm = static_cast(density) / 25.4;
+ image.set("Xres", pixelsPerMm);
+ image.set("Yres", pixelsPerMm);
+ image.set(VIPS_META_RESOLUTION_UNIT, "in");
+ }
+
/*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/
diff --git a/src/common.h b/src/common.h
index a1fcfdb4..be7a2e78 100644
--- a/src/common.h
+++ b/src/common.h
@@ -77,6 +77,21 @@ namespace sharp {
*/
void RemoveExifOrientation(VImage image);
+ /*
+ Does this image have a non-default density?
+ */
+ bool HasDensity(VImage image);
+
+ /*
+ Get pixels/mm resolution as pixels/inch density.
+ */
+ int GetDensity(VImage image);
+
+ /*
+ Set pixels/mm resolution based on a pixels/inch density.
+ */
+ void SetDensity(VImage image, const int density);
+
/*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/
diff --git a/src/metadata.cc b/src/metadata.cc
index 196b6f54..f451fedc 100644
--- a/src/metadata.cc
+++ b/src/metadata.cc
@@ -38,6 +38,8 @@ using sharp::DetermineImageType;
using sharp::HasProfile;
using sharp::HasAlpha;
using sharp::ExifOrientation;
+using sharp::HasDensity;
+using sharp::GetDensity;
using sharp::FreeCallback;
using sharp::counterQueue;
@@ -52,6 +54,7 @@ struct MetadataBaton {
int height;
std::string space;
int channels;
+ int density;
bool hasProfile;
bool hasAlpha;
int orientation;
@@ -63,6 +66,7 @@ struct MetadataBaton {
MetadataBaton():
bufferInLength(0),
+ density(0),
orientation(0),
exifLength(0),
iccLength(0) {}
@@ -120,6 +124,9 @@ class MetadataWorker : public AsyncWorker {
baton->height = image.height();
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image.interpretation());
baton->channels = image.bands();
+ if (HasDensity(image)) {
+ baton->density = GetDensity(image);
+ }
baton->hasProfile = HasProfile(image);
// Derived attributes
baton->hasAlpha = HasAlpha(image);
@@ -161,6 +168,9 @@ class MetadataWorker : public AsyncWorker {
Set(info, New("height").ToLocalChecked(), New(baton->height));
Set(info, New("space").ToLocalChecked(), New(baton->space).ToLocalChecked());
Set(info, New("channels").ToLocalChecked(), New(baton->channels));
+ if (baton->density > 0) {
+ Set(info, New("density").ToLocalChecked(), New(baton->density));
+ }
Set(info, New("hasProfile").ToLocalChecked(), New(baton->hasProfile));
Set(info, New("hasAlpha").ToLocalChecked(), New(baton->hasAlpha));
if (baton->orientation > 0) {
diff --git a/src/operations.cc b/src/operations.cc
index 7cbedbab..be3c97a9 100644
--- a/src/operations.cc
+++ b/src/operations.cc
@@ -169,4 +169,5 @@ namespace sharp {
);
}
}
+
} // namespace sharp
diff --git a/src/operations.h b/src/operations.h
index 7c9d8899..059d8188 100644
--- a/src/operations.h
+++ b/src/operations.h
@@ -32,6 +32,7 @@ namespace sharp {
* Sharpen flat and jagged areas. Use radius of -1 for fast sharpen.
*/
VImage Sharpen(VImage image, int const radius, double const flat, double const jagged);
+
} // namespace sharp
#endif // SRC_OPERATIONS_H_
diff --git a/src/pipeline.cc b/src/pipeline.cc
index 561eff7d..df5770e6 100644
--- a/src/pipeline.cc
+++ b/src/pipeline.cc
@@ -58,6 +58,7 @@ using sharp::HasAlpha;
using sharp::ExifOrientation;
using sharp::SetExifOrientation;
using sharp::RemoveExifOrientation;
+using sharp::SetDensity;
using sharp::IsJpeg;
using sharp::IsPng;
using sharp::IsWebp;
@@ -118,9 +119,12 @@ class PipelineWorker : public AsyncWorker {
try {
VOption *option = VImage::option()->set("access", baton->accessMethod);
if (inputImageType == ImageType::MAGICK) {
- option->set("density", baton->density.data());
+ option->set("density", std::to_string(baton->density).data());
}
image = VImage::new_from_buffer(baton->bufferIn, baton->bufferInLength, nullptr, option);
+ if (inputImageType == ImageType::MAGICK) {
+ SetDensity(image, baton->density);
+ }
} catch (...) {
(baton->err).append("Input buffer has corrupt header");
inputImageType = ImageType::UNKNOWN;
@@ -136,9 +140,12 @@ class PipelineWorker : public AsyncWorker {
try {
VOption *option = VImage::option()->set("access", baton->accessMethod);
if (inputImageType == ImageType::MAGICK) {
- option->set("density", baton->density.data());
+ option->set("density", std::to_string(baton->density).data());
}
image = VImage::new_from_file(baton->fileIn.data(), option);
+ if (inputImageType == ImageType::MAGICK) {
+ SetDensity(image, baton->density);
+ }
} catch (...) {
(baton->err).append("Input file has corrupt header");
inputImageType = ImageType::UNKNOWN;
@@ -921,7 +928,7 @@ NAN_METHOD(pipeline) {
// Limit input images to a given number of pixels, where pixels = width * height
baton->limitInputPixels = attrAs(options, "limitInputPixels");
// Density/DPI at which to load vector images via libmagick
- baton->density = attrAsStr(options, "density");
+ baton->density = attrAs(options, "density");
// Raw pixel input
baton->rawWidth = attrAs(options, "rawWidth");
baton->rawHeight = attrAs(options, "rawHeight");
diff --git a/src/pipeline.h b/src/pipeline.h
index 19c33fab..a5778a29 100644
--- a/src/pipeline.h
+++ b/src/pipeline.h
@@ -19,7 +19,7 @@ struct PipelineBaton {
size_t bufferInLength;
std::string iccProfilePath;
int limitInputPixels;
- std::string density;
+ int density;
int rawWidth;
int rawHeight;
int rawChannels;
@@ -79,7 +79,7 @@ struct PipelineBaton {
PipelineBaton():
bufferInLength(0),
limitInputPixels(0),
- density(""),
+ density(72),
rawWidth(0),
rawHeight(0),
rawChannels(0),
diff --git a/test/unit/io.js b/test/unit/io.js
index 4ae2de36..a776dbf5 100644
--- a/test/unit/io.js
+++ b/test/unit/io.js
@@ -662,7 +662,14 @@ describe('Input/output', function() {
assert.strictEqual('png', info.format);
assert.strictEqual(40, info.width);
assert.strictEqual(40, info.height);
- fixtures.assertSimilar(fixtures.expected('svg72.png'), data, done);
+ fixtures.assertSimilar(fixtures.expected('svg72.png'), data, function(err) {
+ if (err) throw err;
+ sharp(data).metadata(function(err, info) {
+ if (err) throw err;
+ assert.strictEqual(72, info.density);
+ done();
+ });
+ });
}
});
});
@@ -679,7 +686,14 @@ describe('Input/output', function() {
assert.strictEqual('png', info.format);
assert.strictEqual(40, info.width);
assert.strictEqual(40, info.height);
- fixtures.assertSimilar(fixtures.expected('svg1200.png'), data, done);
+ fixtures.assertSimilar(fixtures.expected('svg1200.png'), data, function(err) {
+ if (err) throw err;
+ sharp(data).metadata(function(err, info) {
+ if (err) throw err;
+ assert.strictEqual(1200, info.density);
+ done();
+ });
+ });
}
});
});
diff --git a/test/unit/metadata.js b/test/unit/metadata.js
index 26e48fca..05f9503a 100644
--- a/test/unit/metadata.js
+++ b/test/unit/metadata.js
@@ -18,6 +18,7 @@ describe('Image metadata', function() {
assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
+ assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -35,6 +36,7 @@ describe('Image metadata', function() {
assert.strictEqual(600, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
+ assert.strictEqual(72, metadata.density);
assert.strictEqual(true, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual(8, metadata.orientation);
@@ -64,6 +66,7 @@ describe('Image metadata', function() {
assert.strictEqual(3248, metadata.height);
assert.strictEqual('b-w', metadata.space);
assert.strictEqual(1, metadata.channels);
+ assert.strictEqual(300, metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -82,6 +85,7 @@ describe('Image metadata', function() {
assert.strictEqual(2074, metadata.height);
assert.strictEqual('b-w', metadata.space);
assert.strictEqual(1, metadata.channels);
+ assert.strictEqual(300, metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -99,6 +103,7 @@ describe('Image metadata', function() {
assert.strictEqual(1536, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(4, metadata.channels);
+ assert.strictEqual(72, metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -117,6 +122,7 @@ describe('Image metadata', function() {
assert.strictEqual(772, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
+ assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -135,6 +141,7 @@ describe('Image metadata', function() {
assert.strictEqual(800, metadata.width);
assert.strictEqual(533, metadata.height);
assert.strictEqual(3, metadata.channels);
+ assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -153,6 +160,7 @@ describe('Image metadata', function() {
assert.strictEqual(2220, metadata.width);
assert.strictEqual(2967, metadata.height);
assert.strictEqual(4, metadata.channels);
+ assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual('rgb', metadata.space);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(true, metadata.hasAlpha);
@@ -171,6 +179,7 @@ describe('Image metadata', function() {
assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
+ assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -198,6 +207,7 @@ describe('Image metadata', function() {
assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
+ assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -219,6 +229,7 @@ describe('Image metadata', function() {
assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
+ assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
@@ -238,6 +249,7 @@ describe('Image metadata', function() {
assert.strictEqual(2225, metadata.height);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
+ assert.strictEqual('undefined', typeof metadata.density);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);