Selected output format > unknown file extension #344

This commit is contained in:
Lovell Fuller 2016-02-07 20:13:13 +00:00
parent 5c1067c63f
commit 677b2b9089
8 changed files with 211 additions and 157 deletions

View File

@ -396,7 +396,9 @@ sharp('input.png')
#### toFile(path, [callback]) #### toFile(path, [callback])
`path` is a String containing the path to write the image data to. The format is inferred from the extension, with JPEG, PNG, WebP, TIFF and DZI supported. `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.
`callback`, if present, is called with two arguments `(err, info)` where: `callback`, if present, is called with two arguments `(err, info)` where:

View File

@ -25,6 +25,10 @@
[#340](https://github.com/lovell/sharp/issues/340) [#340](https://github.com/lovell/sharp/issues/340)
[@janaz](https://github.com/janaz) [@janaz](https://github.com/janaz)
* Ensure selected format takes precedence over any unknown output filename extension.
[#344](https://github.com/lovell/sharp/issues/344)
[@ubaltaci](https://github.com/ubaltaci)
* Add support for libvips' PBM, PGM, PPM and FITS image format loaders. * Add support for libvips' PBM, PGM, PPM and FITS image format loaders.
[#347](https://github.com/lovell/sharp/issues/347) [#347](https://github.com/lovell/sharp/issues/347)
[@oaleynik](https://github.com/oaleynik) [@oaleynik](https://github.com/oaleynik)

View File

@ -86,7 +86,8 @@ var Sharp = function(input, options) {
// overlay // overlay
overlayPath: '', overlayPath: '',
// output options // output options
output: '__input', formatOut: 'input',
fileOut: '',
progressive: false, progressive: false,
quality: 80, quality: 80,
compressionLevel: 6, compressionLevel: 6,
@ -667,8 +668,8 @@ Sharp.prototype.limitInputPixels = function(limit) {
/* /*
Write output image data to a file Write output image data to a file
*/ */
Sharp.prototype.toFile = function(output, callback) { Sharp.prototype.toFile = function(fileOut, callback) {
if (!output || output.length === 0) { if (!fileOut || fileOut.length === 0) {
var errOutputInvalid = new Error('Invalid output'); var errOutputInvalid = new Error('Invalid output');
if (typeof callback === 'function') { if (typeof callback === 'function') {
callback(errOutputInvalid); callback(errOutputInvalid);
@ -676,7 +677,7 @@ Sharp.prototype.toFile = function(output, callback) {
return BluebirdPromise.reject(errOutputInvalid); return BluebirdPromise.reject(errOutputInvalid);
} }
} else { } else {
if (this.options.fileIn === output) { if (this.options.fileIn === fileOut) {
var errOutputIsInput = new Error('Cannot use same file for input and output'); var errOutputIsInput = new Error('Cannot use same file for input and output');
if (typeof callback === 'function') { if (typeof callback === 'function') {
callback(errOutputIsInput); callback(errOutputIsInput);
@ -684,7 +685,7 @@ Sharp.prototype.toFile = function(output, callback) {
return BluebirdPromise.reject(errOutputIsInput); return BluebirdPromise.reject(errOutputIsInput);
} }
} else { } else {
this.options.output = output; this.options.fileOut = fileOut;
return this._pipeline(callback); return this._pipeline(callback);
} }
} }
@ -702,7 +703,7 @@ Sharp.prototype.toBuffer = function(callback) {
Force JPEG output Force JPEG output
*/ */
Sharp.prototype.jpeg = function() { Sharp.prototype.jpeg = function() {
this.options.output = '__jpeg'; this.options.formatOut = 'jpeg';
return this; return this;
}; };
@ -710,7 +711,7 @@ Sharp.prototype.jpeg = function() {
Force PNG output Force PNG output
*/ */
Sharp.prototype.png = function() { Sharp.prototype.png = function() {
this.options.output = '__png'; this.options.formatOut = 'png';
return this; return this;
}; };
@ -718,7 +719,7 @@ Sharp.prototype.png = function() {
Force WebP output Force WebP output
*/ */
Sharp.prototype.webp = function() { Sharp.prototype.webp = function() {
this.options.output = '__webp'; this.options.formatOut = 'webp';
return this; return this;
}; };
@ -726,7 +727,7 @@ Sharp.prototype.webp = function() {
Force raw, uint8 output Force raw, uint8 output
*/ */
Sharp.prototype.raw = function() { Sharp.prototype.raw = function() {
this.options.output = '__raw'; this.options.formatOut = 'raw';
return this; return this;
}; };
@ -734,15 +735,17 @@ Sharp.prototype.raw = function() {
Force output to a given format Force output to a given format
@param format is either the id as a String or an Object with an 'id' attribute @param format is either the id as a String or an Object with an 'id' attribute
*/ */
Sharp.prototype.toFormat = function(format) { Sharp.prototype.toFormat = function(formatOut) {
var id = format; if (isObject(formatOut) && isDefined(formatOut.id)) {
if (typeof format === 'object') { formatOut = formatOut.id;
id = format.id;
} }
if (typeof id === 'string' && typeof module.exports.format[id] === 'object' && typeof this[id] === 'function') { if (
this[id](); isDefined(formatOut) &&
['jpeg', 'png', 'webp', 'raw', 'tiff', 'dz', 'input'].indexOf(formatOut) !== -1
) {
this.options.formatOut = formatOut;
} else { } else {
throw new Error('Unsupported format ' + format); throw new Error('Unsupported output format ' + formatOut);
} }
return this; return this;
}; };

View File

@ -53,6 +53,26 @@ namespace sharp {
return EndsWith(str, ".dzi") || EndsWith(str, ".DZI"); return EndsWith(str, ".dzi") || EndsWith(str, ".DZI");
} }
/*
Provide a string identifier for the given image type.
*/
std::string ImageTypeId(ImageType const imageType) {
std::string id;
switch (imageType) {
case ImageType::JPEG: id = "jpeg"; break;
case ImageType::PNG: id = "png"; break;
case ImageType::WEBP: id = "webp"; break;
case ImageType::TIFF: id = "tiff"; break;
case ImageType::MAGICK: id = "magick"; break;
case ImageType::OPENSLIDE: id = "openslide"; break;
case ImageType::PPM: id = "ppm"; break;
case ImageType::FITS: id = "fits"; break;
case ImageType::RAW: id = "raw"; break;
case ImageType::UNKNOWN: id = "unknown"; break;
}
return id;
}
/* /*
Determine image format of a buffer. Determine image format of a buffer.
*/ */

View File

@ -34,6 +34,11 @@ namespace sharp {
bool IsTiff(std::string const &str); bool IsTiff(std::string const &str);
bool IsDz(std::string const &str); bool IsDz(std::string const &str);
/*
Provide a string identifier for the given image type.
*/
std::string ImageTypeId(ImageType const imageType);
/* /*
Determine image format of a buffer. Determine image format of a buffer.
*/ */

View File

@ -33,6 +33,7 @@ using vips::VImage;
using vips::VError; using vips::VError;
using sharp::ImageType; using sharp::ImageType;
using sharp::ImageTypeId;
using sharp::DetermineImageType; using sharp::DetermineImageType;
using sharp::HasProfile; using sharp::HasProfile;
using sharp::HasAlpha; using sharp::HasAlpha;
@ -113,18 +114,7 @@ class MetadataWorker : public AsyncWorker {
} }
if (imageType != ImageType::UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
// Image type // Image type
switch (imageType) { baton->format = ImageTypeId(imageType);
case ImageType::JPEG: baton->format = "jpeg"; break;
case ImageType::PNG: baton->format = "png"; break;
case ImageType::WEBP: baton->format = "webp"; break;
case ImageType::TIFF: baton->format = "tiff"; break;
case ImageType::MAGICK: baton->format = "magick"; break;
case ImageType::OPENSLIDE: baton->format = "openslide"; break;
case ImageType::PPM: baton->format = "ppm"; break;
case ImageType::FITS: baton->format = "fits"; break;
case ImageType::RAW: baton->format = "raw"; break;
case ImageType::UNKNOWN: break;
}
// VipsImage attributes // VipsImage attributes
baton->width = image.width(); baton->width = image.width();
baton->height = image.height(); baton->height = image.height();

View File

@ -49,6 +49,7 @@ using sharp::Blur;
using sharp::Sharpen; using sharp::Sharpen;
using sharp::ImageType; using sharp::ImageType;
using sharp::ImageTypeId;
using sharp::DetermineImageType; using sharp::DetermineImageType;
using sharp::HasProfile; using sharp::HasProfile;
using sharp::HasAlpha; using sharp::HasAlpha;
@ -82,8 +83,8 @@ struct PipelineBaton {
int rawWidth; int rawWidth;
int rawHeight; int rawHeight;
int rawChannels; int rawChannels;
std::string output; std::string formatOut;
std::string outputFormat; std::string fileOut;
void *bufferOut; void *bufferOut;
size_t bufferOutLength; size_t bufferOutLength;
int topOffsetPre; int topOffsetPre;
@ -139,7 +140,8 @@ struct PipelineBaton {
rawWidth(0), rawWidth(0),
rawHeight(0), rawHeight(0),
rawChannels(0), rawChannels(0),
outputFormat(""), formatOut(""),
fileOut(""),
bufferOutLength(0), bufferOutLength(0),
topOffsetPre(-1), topOffsetPre(-1),
topOffsetPost(-1), topOffsetPost(-1),
@ -707,7 +709,9 @@ class PipelineWorker : public AsyncWorker {
} }
// Output // Output
if (baton->output == "__jpeg" || (baton->output == "__input" && inputImageType == ImageType::JPEG)) { if (baton->fileOut == "") {
// Buffer output
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == ImageType::JPEG)) {
// Write JPEG to buffer // Write JPEG to buffer
baton->bufferOut = static_cast<char*>(const_cast<void*>(vips_blob_get(image.jpegsave_buffer(VImage::option() baton->bufferOut = static_cast<char*>(const_cast<void*>(vips_blob_get(image.jpegsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
@ -719,8 +723,8 @@ class PipelineWorker : public AsyncWorker {
->set("optimize_scans", baton->optimiseScans) ->set("optimize_scans", baton->optimiseScans)
->set("interlace", baton->progressive) ->set("interlace", baton->progressive)
), &baton->bufferOutLength))); ), &baton->bufferOutLength)));
baton->outputFormat = "jpeg"; baton->formatOut = "jpeg";
} else if (baton->output == "__png" || (baton->output == "__input" && inputImageType == ImageType::PNG)) { } else if (baton->formatOut == "png" || (baton->formatOut == "input" && inputImageType == ImageType::PNG)) {
// Write PNG to buffer // Write PNG to buffer
baton->bufferOut = static_cast<char*>(const_cast<void*>(vips_blob_get(image.pngsave_buffer(VImage::option() baton->bufferOut = static_cast<char*>(const_cast<void*>(vips_blob_get(image.pngsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
@ -728,15 +732,15 @@ class PipelineWorker : public AsyncWorker {
->set("interlace", baton->progressive) ->set("interlace", baton->progressive)
->set("filter", baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL) ->set("filter", baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL)
), &baton->bufferOutLength))); ), &baton->bufferOutLength)));
baton->outputFormat = "png"; baton->formatOut = "png";
} else if (baton->output == "__webp" || (baton->output == "__input" && inputImageType == ImageType::WEBP)) { } else if (baton->formatOut == "webp" || (baton->formatOut == "input" && inputImageType == ImageType::WEBP)) {
// Write WEBP to buffer // Write WEBP to buffer
baton->bufferOut = static_cast<char*>(const_cast<void*>(vips_blob_get(image.webpsave_buffer(VImage::option() baton->bufferOut = static_cast<char*>(const_cast<void*>(vips_blob_get(image.webpsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->quality) ->set("Q", baton->quality)
), &baton->bufferOutLength))); ), &baton->bufferOutLength)));
baton->outputFormat = "webp"; baton->formatOut = "webp";
} else if (baton->output == "__raw") { } else if (baton->formatOut == "raw") {
// Write raw, uncompressed image data to buffer // Write raw, uncompressed image data to buffer
if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) { if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) {
// Extract first band for greyscale image // Extract first band for greyscale image
@ -752,17 +756,28 @@ class PipelineWorker : public AsyncWorker {
(baton->err).append("Could not allocate enough memory for raw output"); (baton->err).append("Could not allocate enough memory for raw output");
return Error(); return Error();
} }
baton->outputFormat = "raw"; baton->formatOut = "raw";
} else { } else {
bool outputJpeg = IsJpeg(baton->output); // Unsupported output format
bool outputPng = IsPng(baton->output); (baton->err).append("Unsupported output format ");
bool outputWebp = IsWebp(baton->output); if (baton->formatOut == "input") {
bool outputTiff = IsTiff(baton->output); (baton->err).append(ImageTypeId(inputImageType));
bool outputDz = IsDz(baton->output); } else {
bool matchInput = !(outputJpeg || outputPng || outputWebp || outputTiff || outputDz); (baton->err).append(baton->formatOut);
if (outputJpeg || (matchInput && inputImageType == ImageType::JPEG)) { }
return Error();
}
} else {
// File output
bool isJpeg = IsJpeg(baton->fileOut);
bool isPng = IsPng(baton->fileOut);
bool isWebp = IsWebp(baton->fileOut);
bool isTiff = IsTiff(baton->fileOut);
bool isDz = IsDz(baton->fileOut);
bool matchInput = baton->formatOut == "input" && !(isJpeg || isPng || isWebp || isTiff || isDz);
if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
// Write JPEG to file // Write JPEG to file
image.jpegsave(const_cast<char*>(baton->output.data()), VImage::option() image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->quality) ->set("Q", baton->quality)
->set("optimize_coding", TRUE) ->set("optimize_coding", TRUE)
@ -772,41 +787,42 @@ class PipelineWorker : public AsyncWorker {
->set("optimize_scans", baton->optimiseScans) ->set("optimize_scans", baton->optimiseScans)
->set("interlace", baton->progressive) ->set("interlace", baton->progressive)
); );
baton->outputFormat = "jpeg"; baton->formatOut = "jpeg";
} else if (outputPng || (matchInput && inputImageType == ImageType::PNG)) { } else if (baton->formatOut == "png" || isPng || (matchInput && inputImageType == ImageType::PNG)) {
// Write PNG to file // Write PNG to file
image.pngsave(const_cast<char*>(baton->output.data()), VImage::option() image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("compression", baton->compressionLevel) ->set("compression", baton->compressionLevel)
->set("interlace", baton->progressive) ->set("interlace", baton->progressive)
->set("filter", baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL) ->set("filter", baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL)
); );
baton->outputFormat = "png"; baton->formatOut = "png";
} else if (outputWebp || (matchInput && inputImageType == ImageType::WEBP)) { } else if (baton->formatOut == "webp" || isWebp || (matchInput && inputImageType == ImageType::WEBP)) {
// Write WEBP to file // Write WEBP to file
image.webpsave(const_cast<char*>(baton->output.data()), VImage::option() image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->quality) ->set("Q", baton->quality)
); );
baton->outputFormat = "webp"; baton->formatOut = "webp";
} else if (outputTiff || (matchInput && inputImageType == ImageType::TIFF)) { } else if (baton->formatOut == "tiff" || isTiff || (matchInput && inputImageType == ImageType::TIFF)) {
// Write TIFF to file // Write TIFF to file
image.tiffsave(const_cast<char*>(baton->output.data()), VImage::option() image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->quality) ->set("Q", baton->quality)
->set("compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) ->set("compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG)
); );
baton->outputFormat = "tiff"; baton->formatOut = "tiff";
} else if (outputDz) { } else if (baton->formatOut == "dz" || IsDz(baton->fileOut)) {
// Write DZ to file // Write DZ to file
image.dzsave(const_cast<char*>(baton->output.data()), VImage::option() image.dzsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("tile_size", baton->tileSize) ->set("tile_size", baton->tileSize)
->set("overlap", baton->tileOverlap) ->set("overlap", baton->tileOverlap)
); );
baton->outputFormat = "dz"; baton->formatOut = "dz";
} else { } else {
(baton->err).append("Unsupported output " + baton->output); // Unsupported output format
(baton->err).append("Unsupported output format " + baton->fileOut);
return Error(); return Error();
} }
} }
@ -840,7 +856,7 @@ class PipelineWorker : public AsyncWorker {
} }
// Info Object // Info Object
Local<Object> info = New<Object>(); Local<Object> info = New<Object>();
Set(info, New("format").ToLocalChecked(), New<String>(baton->outputFormat).ToLocalChecked()); Set(info, New("format").ToLocalChecked(), New<String>(baton->formatOut).ToLocalChecked());
Set(info, New("width").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(width))); Set(info, New("width").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(width)));
Set(info, New("height").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(height))); Set(info, New("height").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(height)));
Set(info, New("channels").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(baton->channels))); Set(info, New("channels").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(baton->channels)));
@ -856,7 +872,7 @@ class PipelineWorker : public AsyncWorker {
} else { } else {
// Add file size to info // Add file size to info
GStatBuf st; GStatBuf st;
g_stat(baton->output.data(), &st); g_stat(baton->fileOut.data(), &st);
Set(info, New("size").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(st.st_size))); Set(info, New("size").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(st.st_size)));
argv[1] = info; argv[1] = info;
} }
@ -1091,8 +1107,9 @@ NAN_METHOD(pipeline) {
baton->optimiseScans = attrAs<bool>(options, "optimiseScans"); baton->optimiseScans = attrAs<bool>(options, "optimiseScans");
baton->withMetadata = attrAs<bool>(options, "withMetadata"); baton->withMetadata = attrAs<bool>(options, "withMetadata");
baton->withMetadataOrientation = attrAs<int32_t>(options, "withMetadataOrientation"); baton->withMetadataOrientation = attrAs<int32_t>(options, "withMetadataOrientation");
// Output filename or __format for Buffer // Output
baton->output = attrAsStr(options, "output"); baton->formatOut = attrAsStr(options, "formatOut");
baton->fileOut = attrAsStr(options, "fileOut");
baton->tileSize = attrAs<int32_t>(options, "tileSize"); baton->tileSize = attrAs<int32_t>(options, "tileSize");
baton->tileOverlap = attrAs<int32_t>(options, "tileOverlap"); baton->tileOverlap = attrAs<int32_t>(options, "tileOverlap");
// Function to notify of queue length changes // Function to notify of queue length changes

View File

@ -392,10 +392,12 @@ describe('Input/output', function() {
}); });
}); });
describe('Output filename without extension uses input format', function() { describe('Output filename with unknown extension', function() {
it('JPEG', function(done) { it('Match JPEG input', function(done) {
sharp(fixtures.inputJpg).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) { sharp(fixtures.inputJpg)
.resize(320, 80)
.toFile(fixtures.outputZoinks, function(err, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, info.size > 0); assert.strictEqual(true, info.size > 0);
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);
@ -406,8 +408,10 @@ describe('Input/output', function() {
}); });
}); });
it('PNG', function(done) { it('Match PNG input', function(done) {
sharp(fixtures.inputPng).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) { sharp(fixtures.inputPng)
.resize(320, 80)
.toFile(fixtures.outputZoinks, function(err, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, info.size > 0); assert.strictEqual(true, info.size > 0);
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
@ -418,20 +422,11 @@ describe('Input/output', function() {
}); });
}); });
it('Transparent PNG', function(done) {
sharp(fixtures.inputPngWithTransparency).resize(320, 80).toFile(fixtures.outputZoinks, 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(80, info.height);
done();
});
});
if (sharp.format.webp.input.file) { if (sharp.format.webp.input.file) {
it('WebP', function(done) { it('Match WebP input', function(done) {
sharp(fixtures.inputWebP).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) { sharp(fixtures.inputWebP)
.resize(320, 80)
.toFile(fixtures.outputZoinks, function(err, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, info.size > 0); assert.strictEqual(true, info.size > 0);
assert.strictEqual('webp', info.format); assert.strictEqual('webp', info.format);
@ -443,8 +438,10 @@ describe('Input/output', function() {
}); });
} }
it('TIFF', function(done) { it('Match TIFF input', function(done) {
sharp(fixtures.inputTiff).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) { sharp(fixtures.inputTiff)
.resize(320, 80)
.toFile(fixtures.outputZoinks, function(err, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, info.size > 0); assert.strictEqual(true, info.size > 0);
assert.strictEqual('tiff', info.format); assert.strictEqual('tiff', info.format);
@ -455,13 +452,29 @@ describe('Input/output', function() {
}); });
}); });
it('Fail with GIF', function(done) { it('Match GIF input, therefore fail', function(done) {
sharp(fixtures.inputGif).resize(320, 80).toFile(fixtures.outputZoinks, function(err) { sharp(fixtures.inputGif)
.resize(320, 80)
.toFile(fixtures.outputZoinks, function(err) {
assert(!!err); assert(!!err);
done(); done();
}); });
}); });
it('Force JPEG format for PNG input', function(done) {
sharp(fixtures.inputPng)
.resize(320, 80)
.jpeg()
.toFile(fixtures.outputZoinks, function(err, info) {
if (err) throw err;
assert.strictEqual(true, info.size > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
fs.unlinkSync(fixtures.outputZoinks);
done();
});
});
}); });
describe('PNG output', function() { describe('PNG output', function() {