diff --git a/docs/api.md b/docs/api.md index 564df25a..f2f9573e 100644 --- a/docs/api.md +++ b/docs/api.md @@ -507,7 +507,7 @@ In the above example if `input.png` is a 3 channel RGB image, `output.png` will `path` is a String containing the path to write the image data to. -If an explicit output format is not selected, it will be inferred from the extension, with JPEG, PNG, WebP, TIFF and DZI supported. Note that RAW format is only supported for buffer output. +If an explicit output format is not selected, it will be inferred from the extension, with JPEG, PNG, WebP, TIFF, DZI, and VIPS V format supported. Note that RAW format is only supported for buffer output. `callback`, if present, is called with two arguments `(err, info)` where: diff --git a/index.js b/index.js index 40dd8133..414fe206 100644 --- a/index.js +++ b/index.js @@ -975,7 +975,7 @@ Sharp.prototype.toFormat = function(formatOut) { } if ( isDefined(formatOut) && - ['jpeg', 'png', 'webp', 'raw', 'tiff', 'dz', 'input'].indexOf(formatOut) !== -1 + ['jpeg', 'png', 'webp', 'raw', 'tiff', 'dz', 'input'].indexOf(formatOut) !== -1 ) { this.options.formatOut = formatOut; } else { diff --git a/src/common.cc b/src/common.cc index 71f163d0..1045c39e 100644 --- a/src/common.cc +++ b/src/common.cc @@ -55,6 +55,9 @@ namespace sharp { bool IsDzZip(std::string const &str) { return EndsWith(str, ".zip") || EndsWith(str, ".ZIP") || EndsWith(str, ".szi") || EndsWith(str, ".SZI"); } + bool IsV(std::string const &str) { + return EndsWith(str, ".v") || EndsWith(str, ".V") || EndsWith(str, ".vips") || EndsWith(str, ".VIPS"); + } /* Provide a string identifier for the given image type. @@ -73,6 +76,7 @@ namespace sharp { case ImageType::OPENSLIDE: id = "openslide"; break; case ImageType::PPM: id = "ppm"; break; case ImageType::FITS: id = "fits"; break; + case ImageType::VIPS: id = "v"; break; case ImageType::RAW: id = "raw"; break; case ImageType::UNKNOWN: id = "unknown"; break; } @@ -136,6 +140,8 @@ namespace sharp { imageType = ImageType::PPM; } else if (EndsWith(loader, "Fits")) { imageType = ImageType::FITS; + } else if (EndsWith(loader, "Vips")) { + imageType = ImageType::VIPS; } else if (EndsWith(loader, "Magick") || EndsWith(loader, "MagickFile")) { imageType = ImageType::MAGICK; } diff --git a/src/common.h b/src/common.h index b8d6afc1..511fc804 100644 --- a/src/common.h +++ b/src/common.h @@ -22,6 +22,7 @@ namespace sharp { OPENSLIDE, PPM, FITS, + VIPS, RAW, UNKNOWN }; @@ -39,6 +40,7 @@ namespace sharp { bool IsTiff(std::string const &str); bool IsDz(std::string const &str); bool IsDzZip(std::string const &str); + bool IsV(std::string const &str); /* Provide a string identifier for the given image type. diff --git a/src/pipeline.cc b/src/pipeline.cc index a2721449..c3ac736d 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -73,6 +73,7 @@ using sharp::IsWebp; using sharp::IsTiff; using sharp::IsDz; using sharp::IsDzZip; +using sharp::IsV; using sharp::FreeCallback; using sharp::CalculateCrop; using sharp::counterProcess; @@ -878,7 +879,9 @@ class PipelineWorker : public AsyncWorker { bool isTiff = IsTiff(baton->fileOut); bool isDz = IsDz(baton->fileOut); bool isDzZip = IsDzZip(baton->fileOut); - bool matchInput = baton->formatOut == "input" && !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip); + bool isV = IsV(baton->fileOut); + bool matchInput = baton->formatOut == "input" && + !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV); if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) { // Write JPEG to file image.jpegsave(const_cast(baton->fileOut.data()), VImage::option() @@ -932,6 +935,12 @@ class PipelineWorker : public AsyncWorker { ->set("layout", baton->tileLayout) ); baton->formatOut = "dz"; + } else if (baton->formatOut == "v" || isV || (matchInput && inputImageType == ImageType::VIPS)) { + // Write V to file + image.vipssave(const_cast(baton->fileOut.data()), VImage::option() + ->set("strip", !baton->withMetadata) + ); + baton->formatOut = "v"; } else { // Unsupported output format (baton->err).append("Unsupported output format " + baton->fileOut); diff --git a/src/utilities.cc b/src/utilities.cc index 3cc686d2..8dae0bd9 100644 --- a/src/utilities.cc +++ b/src/utilities.cc @@ -139,7 +139,7 @@ NAN_METHOD(format) { // Which load/save operations are available for each compressed format? Local format = New(); for (std::string f : { - "jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", "ppm", "fits", "gif", "svg", "pdf" + "jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", "ppm", "fits", "gif", "svg", "pdf", "v" }) { // Input Local hasInputFile = diff --git a/test/fixtures/expected/vfile.jpg b/test/fixtures/expected/vfile.jpg new file mode 100644 index 00000000..9d1e4a57 Binary files /dev/null and b/test/fixtures/expected/vfile.jpg differ diff --git a/test/fixtures/index.js b/test/fixtures/index.js index 3555a93c..65df6673 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -95,9 +95,12 @@ module.exports = { inputPngStripesV: getPath('stripesV.png'), inputPngStripesH: getPath('stripesH.png'), + inputV: getPath('vfile.v'), + outputJpg: getPath('output.jpg'), outputPng: getPath('output.png'), outputWebP: getPath('output.webp'), + outputV: getPath('output.v'), outputZoinks: getPath('output.zoinks'), // an 'unknown' file extension // Path for tests requiring human inspection diff --git a/test/fixtures/vfile.v b/test/fixtures/vfile.v new file mode 100644 index 00000000..d0fb6000 Binary files /dev/null and b/test/fixtures/vfile.v differ diff --git a/test/unit/io.js b/test/unit/io.js index 472dc0f9..83649d2a 100644 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -781,6 +781,37 @@ describe('Input/output', function() { }); } + if (sharp.format.v.input.file) { + it("Load Vips V file", function(done) { + sharp(fixtures.inputV) + .jpeg() + .toBuffer(function(err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(70, info.width); + assert.strictEqual(60, info.height); + fixtures.assertSimilar(fixtures.expected('vfile.jpg'), data, done); + }); + }); + } + + if (sharp.format.v.output.file) { + it("Save Vips V file", function(done) { + sharp(fixtures.inputJpg) + .extract({left: 910, top: 1105, width: 70, height: 60}) + .toFile(fixtures.outputV, function(err, info) { + if(err) throw err; + assert.strictEqual(true, info.size > 0); + assert.strictEqual('v', info.format); + assert.strictEqual(70, info.width); + assert.strictEqual(60, info.height); + fs.unlinkSync(fixtures.outputV); + done(); + }); + }); + } + if (sharp.format.raw.output.buffer) { describe('Ouput raw, uncompressed image data', function() { it('1 channel greyscale image', function(done) {