Skip to content

Commit

Permalink
Expose runtime format availability
Browse files Browse the repository at this point in the history
Aids addition of new format/method combos

Dogfood this in the test code
  • Loading branch information
lovell committed Feb 26, 2015
1 parent 1565522 commit c7ccf68
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 43 deletions.
36 changes: 35 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,8 +218,42 @@ sharp(inputBuffer)
});
```

```javascript
// Runtime discovery of available formats
console.dir(sharp.format);
```

## API

### Attributes

#### format

An Object containing nested boolean values
representing the available input and output formats/methods,
for example:

```json
{ jpeg: { id: 'jpeg',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: true, stream: true } },
png: { id: 'png',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: true, stream: true } },
webp: { id: 'webp',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: true, stream: true } },
tiff: { id: 'tiff',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: false, stream: false } },
magick: { id: 'magick',
input: { file: true, buffer: true, stream: true },
output: { file: false, buffer: false, stream: false } },
raw: { id: 'raw',
input: { file: false, buffer: false, stream: false },
output: { file: false, buffer: true, stream: true } } }
```

### Input methods

#### sharp([input])
Expand All @@ -235,7 +269,7 @@ JPEG, PNG, WebP, GIF* or TIFF format image data can be streamed into the object

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

\* GIF support requires libvips 8.0.0+.
\* libvips 8.0.0+ is required for Buffer/Stream input of GIF and other `magick` formats.

#### metadata([callback])

Expand Down
22 changes: 14 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ var Sharp = function(input) {
module.exports = Sharp;
util.inherits(Sharp, stream.Duplex);

/*
Supported image formats
*/
module.exports.format = sharp.format();

/*
Handle incoming chunk on Writable Stream
*/
Expand Down Expand Up @@ -481,7 +486,8 @@ Sharp.prototype.webp = function() {
Force raw, uint8 output
*/
Sharp.prototype.raw = function() {
if (semver.gte(libvipsVersion, '7.42.0')) {
var supportsRawOutput = module.exports.format.raw.output;
if (supportsRawOutput.file || supportsRawOutput.buffer || supportsRawOutput.stream) {
this.options.output = '__raw';
} else {
console.error('Raw output requires libvips 7.42.0+');
Expand All @@ -491,15 +497,15 @@ Sharp.prototype.raw = function() {

/*
Force output to a given format
@param format is either the id as a String or an Object with an 'id' attribute
*/
module.exports.format = {'jpeg': 'jpeg', 'png': 'png', 'webp': 'webp', 'raw': 'raw'};
Sharp.prototype.toFormat = function(format) {
if (
typeof format === 'string' &&
typeof module.exports.format[format] === 'string' &&
typeof this[format] === 'function'
) {
this[format]();
var id = format;
if (typeof format === 'object') {
id = format.id;
}
if (typeof id === 'string' && typeof module.exports.format[id] === 'object' && typeof this[id] === 'function') {
this[id]();
} else {
throw new Error('Unsupported format ' + format);
}
Expand Down
1 change: 1 addition & 0 deletions src/sharp.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ extern "C" void init(v8::Handle<v8::Object> target) {
NODE_SET_METHOD(target, "concurrency", concurrency);
NODE_SET_METHOD(target, "counters", counters);
NODE_SET_METHOD(target, "libvipsVersion", libvipsVersion);
NODE_SET_METHOD(target, "format", format);
}

NODE_MODULE(sharp, init)
64 changes: 64 additions & 0 deletions src/utilities.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ using v8::Local;
using v8::Object;
using v8::Number;
using v8::String;
using v8::Boolean;

using sharp::counterQueue;
using sharp::counterProcess;
Expand Down Expand Up @@ -78,3 +79,66 @@ NAN_METHOD(libvipsVersion) {
snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
NanReturnValue(NanNew<String>(version));
}

/*
Get available input/output file/buffer/stream formats
*/
NAN_METHOD(format) {
NanScope();

// Attribute names
Local<String> attrId = NanNew<String>("id");
Local<String> attrInput = NanNew<String>("input");
Local<String> attrOutput = NanNew<String>("output");
Local<String> attrFile = NanNew<String>("file");
Local<String> attrBuffer = NanNew<String>("buffer");
Local<String> attrStream = NanNew<String>("stream");

// Which load/save operations are available for each compressed format?
Local<Object> format = NanNew<Object>();
for (std::string f : {"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz"}) {
// Input
Local<Object> input = NanNew<Object>();
input->Set(attrFile, NanNew<Boolean>(
vips_type_find("VipsOperation", (f + "load").c_str())));
input->Set(attrBuffer, NanNew<Boolean>(
vips_type_find("VipsOperation", (f + "load_buffer").c_str())));
input->Set(attrStream, input->Get(attrBuffer));
// Output
Local<Object> output = NanNew<Object>();
output->Set(attrFile, NanNew<Boolean>(
vips_type_find("VipsOperation", (f + "save").c_str())));
output->Set(attrBuffer, NanNew<Boolean>(
vips_type_find("VipsOperation", (f + "save_buffer").c_str())));
output->Set(attrStream, output->Get(attrBuffer));
// Other attributes
Local<Object> container = NanNew<Object>();
Local<String> formatId = NanNew<String>(f);
container->Set(attrId, formatId);
container->Set(attrInput, input);
container->Set(attrOutput, output);
// Add to set of formats
format->Set(formatId, container);
}

// Raw, uncompressed data
Local<Object> raw = NanNew<Object>();
raw->Set(attrId, NanNew<String>("raw"));
format->Set(NanNew<String>("raw"), raw);
// No support for raw input yet, so always false
Local<Boolean> unsupported = NanNew<Boolean>(false);
Local<Object> rawInput = NanNew<Object>();
rawInput->Set(attrFile, unsupported);
rawInput->Set(attrBuffer, unsupported);
rawInput->Set(attrStream, unsupported);
raw->Set(attrInput, rawInput);
// Raw output via Buffer/Stream is available in libvips >= 7.42.0
Local<Boolean> supportsRawOutput = NanNew<Boolean>(vips_version(0) >= 8 || (vips_version(0) == 7 && vips_version(1) >= 42));
Local<Object> rawOutput = NanNew<Object>();
rawOutput->Set(attrFile, unsupported);
rawOutput->Set(attrBuffer, supportsRawOutput);
rawOutput->Set(attrStream, supportsRawOutput);
raw->Set(attrOutput, rawOutput);

NanReturnValue(format);
}
1 change: 1 addition & 0 deletions src/utilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ NAN_METHOD(cache);
NAN_METHOD(concurrency);
NAN_METHOD(counters);
NAN_METHOD(libvipsVersion);
NAN_METHOD(format);

#endif // SRC_UTILITIES_H_
72 changes: 38 additions & 34 deletions test/unit/io.js
Original file line number Diff line number Diff line change
Expand Up @@ -488,39 +488,43 @@ describe('Input/output', function() {
});
});

it('Convert SVG, if supported, to PNG', function(done) {
sharp(fixtures.inputSvg)
.resize(100, 100)
.toFormat('png')
.toFile(fixtures.path('output.svg.png'), function(err, info) {
if (err) {
assert.strictEqual('Input file is of an unsupported image format', err.message);
} else {
if (sharp.format.magick.input.file) {
it('Convert SVG, if supported, to PNG', function(done) {
sharp(fixtures.inputSvg)
.resize(100, 100)
.toFormat('png')
.toFile(fixtures.path('output.svg.png'), function(err, info) {
if (err) {
assert.strictEqual('Input file is of an unsupported image format', err.message);
} else {
assert.strictEqual(true, info.size > 0);
assert.strictEqual('png', info.format);
assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height);
}
done();
});
});
}

if (sharp.format.magick.input.file) {
it('Convert PSD to PNG', function(done) {
sharp(fixtures.inputPsd)
.resize(320, 240)
.toFormat(sharp.format.png)
.toFile(fixtures.path('output.psd.png'), function(err, info) {
if (err) throw err;
assert.strictEqual(true, info.size > 0);
assert.strictEqual('png', info.format);
assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height);
}
done();
});
});

it('Convert PSD to PNG', function(done) {
sharp(fixtures.inputPsd)
.resize(320, 240)
.toFormat(sharp.format.png)
.toFile(fixtures.path('output.psd.png'), function(err, info) {
if (err) throw err;
assert.strictEqual(true, info.size > 0);
assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
}

if (semver.gte(sharp.libvipsVersion(), '7.40.0')) {
it('Load TIFF from Buffer [libvips ' + sharp.libvipsVersion() + '>=7.40.0]', function(done) {
if (sharp.format.tiff.input.buffer) {
it('Load TIFF from Buffer', function(done) {
var inputTiffBuffer = fs.readFileSync(fixtures.inputTiff);
sharp(inputTiffBuffer)
.resize(320, 240)
Expand All @@ -537,8 +541,8 @@ describe('Input/output', function() {
});
}

if (semver.gte(sharp.libvipsVersion(), '8.0.0')) {
it('Load GIF from Buffer [libvips ' + sharp.libvipsVersion() + '>=8.0.0]', function(done) {
if (sharp.format.magick.input.buffer) {
it('Load GIF from Buffer', function(done) {
var inputGifBuffer = fs.readFileSync(fixtures.inputGif);
sharp(inputGifBuffer)
.resize(320, 240)
Expand All @@ -555,8 +559,8 @@ describe('Input/output', function() {
});
}

if (semver.gte(sharp.libvipsVersion(), '7.42.0')) {
describe('Ouput raw, uncompressed image data [libvips ' + sharp.libvipsVersion() + '>=7.42.0]', function() {
if (sharp.format.raw.output.buffer) {
describe('Ouput raw, uncompressed image data', function() {
it('1 channel greyscale image', function(done) {
sharp(fixtures.inputJpg)
.greyscale()
Expand Down
21 changes: 21 additions & 0 deletions test/unit/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,25 @@ describe('Utilities', function() {
});
});

describe('Format', function() {
it('Contains expected attributes', function() {
assert.strictEqual('object', typeof sharp.format);
Object.keys(sharp.format).forEach(function(format) {
assert.strictEqual(true, 'id' in sharp.format[format]);
assert.strictEqual(format, sharp.format[format].id);
['input', 'output'].forEach(function(direction) {
assert.strictEqual(true, direction in sharp.format[format]);
assert.strictEqual('object', typeof sharp.format[format][direction]);
assert.strictEqual(3, Object.keys(sharp.format[format][direction]).length);
assert.strictEqual(true, 'file' in sharp.format[format][direction]);
assert.strictEqual(true, 'buffer' in sharp.format[format][direction]);
assert.strictEqual(true, 'stream' in sharp.format[format][direction]);
assert.strictEqual('boolean', typeof sharp.format[format][direction].file);
assert.strictEqual('boolean', typeof sharp.format[format][direction].buffer);
assert.strictEqual('boolean', typeof sharp.format[format][direction].stream);
});
});
});
});

});

0 comments on commit c7ccf68

Please sign in to comment.