diff --git a/README.md b/README.md index 90abfa71..ddd19053 100755 --- a/README.md +++ b/README.md @@ -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]) @@ -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]) diff --git a/index.js b/index.js index 86887bbe..9e0eca33 100755 --- a/index.js +++ b/index.js @@ -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 */ @@ -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+'); @@ -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); } diff --git a/src/sharp.cc b/src/sharp.cc index 1e9e7e8c..bab73c9f 100755 --- a/src/sharp.cc +++ b/src/sharp.cc @@ -23,6 +23,7 @@ extern "C" void init(v8::Handle 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) diff --git a/src/utilities.cc b/src/utilities.cc index 7911b3de..8c6de0ce 100755 --- a/src/utilities.cc +++ b/src/utilities.cc @@ -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; @@ -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(version)); } + +/* + Get available input/output file/buffer/stream formats +*/ +NAN_METHOD(format) { + NanScope(); + + // Attribute names + Local attrId = NanNew("id"); + Local attrInput = NanNew("input"); + Local attrOutput = NanNew("output"); + Local attrFile = NanNew("file"); + Local attrBuffer = NanNew("buffer"); + Local attrStream = NanNew("stream"); + + // Which load/save operations are available for each compressed format? + Local format = NanNew(); + for (std::string f : {"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz"}) { + // Input + Local input = NanNew(); + input->Set(attrFile, NanNew( + vips_type_find("VipsOperation", (f + "load").c_str()))); + input->Set(attrBuffer, NanNew( + vips_type_find("VipsOperation", (f + "load_buffer").c_str()))); + input->Set(attrStream, input->Get(attrBuffer)); + // Output + Local output = NanNew(); + output->Set(attrFile, NanNew( + vips_type_find("VipsOperation", (f + "save").c_str()))); + output->Set(attrBuffer, NanNew( + vips_type_find("VipsOperation", (f + "save_buffer").c_str()))); + output->Set(attrStream, output->Get(attrBuffer)); + // Other attributes + Local container = NanNew(); + Local formatId = NanNew(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 raw = NanNew(); + raw->Set(attrId, NanNew("raw")); + format->Set(NanNew("raw"), raw); + // No support for raw input yet, so always false + Local unsupported = NanNew(false); + Local rawInput = NanNew(); + 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 supportsRawOutput = NanNew(vips_version(0) >= 8 || (vips_version(0) == 7 && vips_version(1) >= 42)); + Local rawOutput = NanNew(); + rawOutput->Set(attrFile, unsupported); + rawOutput->Set(attrBuffer, supportsRawOutput); + rawOutput->Set(attrStream, supportsRawOutput); + raw->Set(attrOutput, rawOutput); + + NanReturnValue(format); +} diff --git a/src/utilities.h b/src/utilities.h index 7c319aa6..8fe53020 100755 --- a/src/utilities.h +++ b/src/utilities.h @@ -7,5 +7,6 @@ NAN_METHOD(cache); NAN_METHOD(concurrency); NAN_METHOD(counters); NAN_METHOD(libvipsVersion); +NAN_METHOD(format); #endif // SRC_UTILITIES_H_ diff --git a/test/unit/io.js b/test/unit/io.js index 101a13c4..5f924c87 100755 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -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(); - }); - }); + assert.strictEqual(320, info.width); + assert.strictEqual(240, 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(); - }); - }); - - 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) @@ -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) @@ -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() diff --git a/test/unit/util.js b/test/unit/util.js index e1e7291d..39304ee0 100755 --- a/test/unit/util.js +++ b/test/unit/util.js @@ -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); + }); + }); + }); + }); + });