Skip to content

Commit

Permalink
Control density/DPI when loading vectors via libmagick #110
Browse files Browse the repository at this point in the history
  • Loading branch information
lovell committed Feb 1, 2016
1 parent 906fb67 commit 3ab0e8c
Show file tree
Hide file tree
Showing 11 changed files with 102 additions and 40 deletions.
22 changes: 13 additions & 9 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,26 @@ var sharp = require('sharp');

### Input

#### sharp([input])
#### sharp([input], [options])

Constructor to which further methods are chained. `input`, if present, can be one of:
Constructor to which further methods are chained.

* Buffer containing JPEG, PNG, WebP, GIF* or TIFF image data, or
`input`, if present, can be one of:

* Buffer containing JPEG, PNG, WebP, GIF, SVG or TIFF image data, or
* String containing the path to an image file, with most major formats supported.

The object returned implements the
[stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
JPEG, PNG, WebP, GIF, SVG or TIFF format image data
can be streamed into the object when `input` is `null` or `undefined`.

JPEG, PNG, WebP, GIF* or TIFF format image data
can be streamed into the object when `input` is not provided.
`options`, if present, is an Object with the following optional attributes:

JPEG, PNG or WebP format image data can be streamed out from this object.
* `density` an integral number representing the DPI for vector images, defaulting to 72.

\* libvips 8.0.0+ is required for Buffer/Stream input of GIF and other `magick` formats.
The object returned by the constructor implements the
[stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.

JPEG, PNG or WebP format image data can be streamed out from this object.

```javascript
sharp('input.jpg')
Expand Down
5 changes: 5 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@

### v0.13 - "*mind*"

* Control density/DPI when loading vector images via libmagick.
[#110](https://github.com/lovell/sharp/issues/110)
[@bradisbell](https://github.com/bradisbell)

* Switch from libvips' C to C++ bindings, requires upgrade to v8.2.2.
[#299](https://github.com/lovell/sharp/issues/299)

* Control number of open files in libvips' cache; breaks existing `cache` behaviour.
[#315](https://github.com/lovell/sharp/issues/315)
[@impomezia](https://github.com/impomezia)

* Ensure 16-bit input images can be embedded onto a transparent background.
[#340](https://github.com/lovell/sharp/issues/340)
Expand Down
29 changes: 26 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ var maximum = {
};

// Constructor-factory
var Sharp = function(input) {
var Sharp = function(input, options) {
if (!(this instanceof Sharp)) {
return new Sharp(input);
return new Sharp(input, options);
}
stream.Duplex.call(this);
this.options = {
Expand All @@ -46,6 +46,7 @@ var Sharp = function(input) {
streamIn: false,
sequentialRead: false,
limitInputPixels: maximum.pixels,
density: '72',
// ICC profiles
iccProfilePath: path.join(__dirname, 'icc') + path.sep,
// resize options
Expand Down Expand Up @@ -107,12 +108,13 @@ var Sharp = function(input) {
} else if (typeof input === 'object' && input instanceof Buffer) {
// input=buffer
this.options.bufferIn = input;
} else if (typeof input === 'undefined') {
} else if (typeof input === 'undefined' || input === null) {
// input=stream
this.options.streamIn = true;
} else {
throw new Error('Unsupported input ' + typeof input);
}
this._inputOptions(options);
return this;
};
module.exports = Sharp;
Expand All @@ -133,6 +135,27 @@ module.exports.format = sharp.format();
*/
module.exports.versions = versions;

/*
Set input-related options
density: DPI at which to load vector images via libmagick
*/
Sharp.prototype._inputOptions = function(options) {
if (typeof options === 'object') {
if (typeof options.density !== 'undefined') {
if (
typeof options.density === 'number' && !Number.isNaN(options.density) &&
options.density % 1 === 0 && options.density > 0 && options.density <= 2400
) {
this.options.density = options.density.toString();
} else {
throw new Error('Invalid density (1 to 2400)' + options.density);
}
}
} else if (typeof options !== 'undefined' && options !== null) {
throw new Error('Invalid input options ' + options);
}
};

/*
Handle incoming chunk on Writable Stream
*/
Expand Down
14 changes: 10 additions & 4 deletions src/pipeline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ struct PipelineBaton {
size_t bufferInLength;
std::string iccProfilePath;
int limitInputPixels;
std::string density;
std::string output;
std::string outputFormat;
void *bufferOut;
Expand Down Expand Up @@ -129,6 +130,7 @@ struct PipelineBaton {
PipelineBaton():
bufferInLength(0),
limitInputPixels(0),
density(""),
outputFormat(""),
bufferOutLength(0),
topOffsetPre(-1),
Expand Down Expand Up @@ -201,8 +203,9 @@ class PipelineWorker : public AsyncWorker {
if (inputImageType != ImageType::UNKNOWN) {
try {
image = VImage::new_from_buffer(
baton->bufferIn, baton->bufferInLength, nullptr,
VImage::option()->set("access", baton->accessMethod)
baton->bufferIn, baton->bufferInLength, nullptr, VImage::option()
->set("access", baton->accessMethod)
->set("density", baton->density.data())
);
} catch (...) {
(baton->err).append("Input buffer has corrupt header");
Expand All @@ -217,8 +220,9 @@ class PipelineWorker : public AsyncWorker {
if (inputImageType != ImageType::UNKNOWN) {
try {
image = VImage::new_from_file(
baton->fileIn.data(),
VImage::option()->set("access", baton->accessMethod)
baton->fileIn.data(), VImage::option()
->set("access", baton->accessMethod)
->set("density", baton->density.data())
);
} catch (...) {
(baton->err).append("Input file has corrupt header");
Expand Down Expand Up @@ -997,6 +1001,8 @@ NAN_METHOD(pipeline) {
baton->iccProfilePath = attrAsStr(options, "iccProfilePath");
// Limit input images to a given number of pixels, where pixels = width * height
baton->limitInputPixels = attrAs<int32_t>(options, "limitInputPixels");
// Density/DPI at which to load vector images via libmagick
baton->density = attrAsStr(options, "density");
// Extract image options
baton->topOffsetPre = attrAs<int32_t>(options, "topOffsetPre");
baton->leftOffsetPre = attrAs<int32_t>(options, "leftOffsetPre");
Expand Down
17 changes: 0 additions & 17 deletions test/fixtures/Wikimedia-logo.svg

This file was deleted.

3 changes: 3 additions & 0 deletions test/fixtures/check.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed test/fixtures/expected/svg.png
Binary file not shown.
Binary file added test/fixtures/expected/svg1200.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/fixtures/expected/svg72.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion test/fixtures/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ module.exports = {
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
inputSvg: getPath('Wikimedia-logo.svg'), // http://commons.wikimedia.org/wiki/File:Wikimedia-logo.svg
inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
inputPsd: getPath('free-gearhead-pack.psd'), // https://dribbble.com/shots/1624241-Free-Gearhead-Vector-Pack

inputSvs: getPath('CMU-1-Small-Region.svs'), // http://openslide.cs.cmu.edu/download/openslide-testdata/Aperio/CMU-1-Small-Region.svs
Expand Down
50 changes: 44 additions & 6 deletions test/unit/io.js
Original file line number Diff line number Diff line change
Expand Up @@ -634,20 +634,37 @@ describe('Input/output', function() {
});

if (sharp.format.magick.input.file) {
it('Convert SVG, if supported, to PNG', function(done) {
it('Convert SVG to PNG at default 72DPI', function(done) {
sharp(fixtures.inputSvg)
.resize(100, 100)
.resize(1024)
.extract({left: 290, top: 760, width: 40, height: 40})
.toFormat('png')
.toBuffer(function(err, data, info) {
if (err) {
assert.strictEqual(0, err.message.indexOf('Input file is missing or of an unsupported image format'));
done();
} else {
assert.strictEqual(true, info.size > 0);
assert.strictEqual('png', info.format);
assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height);
fixtures.assertSimilar(fixtures.expected('svg.png'), data, done);
assert.strictEqual(40, info.width);
assert.strictEqual(40, info.height);
fixtures.assertSimilar(fixtures.expected('svg72.png'), data, done);
}
});
});
it('Convert SVG to PNG at 300DPI', function(done) {
sharp(fixtures.inputSvg, { density: 1200 })
.resize(1024)
.extract({left: 290, top: 760, width: 40, height: 40})
.toFormat('png')
.toBuffer(function(err, data, info) {
if (err) {
assert.strictEqual(0, err.message.indexOf('Input file is missing or of an unsupported image format'));
done();
} else {
assert.strictEqual('png', info.format);
assert.strictEqual(40, info.width);
assert.strictEqual(40, info.height);
fixtures.assertSimilar(fixtures.expected('svg1200.png'), data, done);
}
});
});
Expand Down Expand Up @@ -824,6 +841,27 @@ describe('Input/output', function() {

});

describe('Input options', function() {
it('Non-Object options fails', function() {
assert.throws(function() {
sharp(null, 'zoinks');
});
});
it('Invalid density: string', function() {
assert.throws(function() {
sharp(null, { density: 'zoinks' } );
});
});
it('Invalid density: float', function() {
assert.throws(function() {
sharp(null, { density: 0.5 } );
});
});
it('Ignore unknown attribute', function() {
sharp(null, { unknown: true } );
});
});

it('Queue length change events', function(done) {
var eventCounter = 0;
var queueListener = function(queueLength) {
Expand Down

0 comments on commit 3ab0e8c

Please sign in to comment.