mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Add features from libvips 7.40+
Load TIFF from Buffer/Stream Interlaced PNG output no longer needs tilecache Option to disable PNG adaptive row filtering
This commit is contained in:
parent
740838b47c
commit
7537adf399
12
README.md
12
README.md
@ -29,7 +29,7 @@ This module is powered by the blazingly fast [libvips](https://github.com/jcupit
|
||||
### Prerequisites
|
||||
|
||||
* Node.js v0.10+
|
||||
* [libvips](https://github.com/jcupitt/libvips) v7.38.5+
|
||||
* [libvips](https://github.com/jcupitt/libvips) v7.38.5+ (7.40.9+ recommended)
|
||||
|
||||
To install the latest version of libvips on the following Operating Systems:
|
||||
|
||||
@ -214,12 +214,12 @@ sharp(inputBuffer)
|
||||
|
||||
Constructor to which further methods are chained. `input`, if present, can be one of:
|
||||
|
||||
* Buffer containing JPEG, PNG or WebP image data, or
|
||||
* Buffer containing JPEG, PNG, WebP or TIFF (libvips 7.40.0+) image data, or
|
||||
* String containing the filename of an image, 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 or WebP format image data can be streamed into the object when `input` is not provided.
|
||||
JPEG, PNG, WebP or TIFF (libvips 7.40.0+) format image data can be streamed into the object when `input` is not provided.
|
||||
|
||||
JPEG, PNG or WebP format image data can be streamed out from this object.
|
||||
|
||||
@ -388,6 +388,12 @@ An advanced setting for the _zlib_ compression level of the lossless PNG output
|
||||
|
||||
`compressionLevel` is a Number between 0 and 9.
|
||||
|
||||
#### withoutAdaptiveFiltering()
|
||||
|
||||
_Requires libvips 7.41.0+_
|
||||
|
||||
An advanced and experimental PNG output setting to disable adaptive row filtering.
|
||||
|
||||
### Output methods
|
||||
|
||||
#### toFile(filename, [callback])
|
||||
|
43
index.js
43
index.js
@ -4,10 +4,12 @@ var path = require('path');
|
||||
var util = require('util');
|
||||
var stream = require('stream');
|
||||
|
||||
var semver = require('semver');
|
||||
var color = require('color');
|
||||
var BluebirdPromise = require('bluebird');
|
||||
|
||||
var sharp = require('./build/Release/sharp');
|
||||
var libvipsVersion = sharp.libvipsVersion();
|
||||
|
||||
var Sharp = function(input) {
|
||||
if (!(this instanceof Sharp)) {
|
||||
@ -49,6 +51,7 @@ var Sharp = function(input) {
|
||||
progressive: false,
|
||||
quality: 80,
|
||||
compressionLevel: 6,
|
||||
withoutAdaptiveFiltering: false,
|
||||
streamOut: false,
|
||||
withMetadata: false
|
||||
};
|
||||
@ -58,14 +61,22 @@ var Sharp = function(input) {
|
||||
} else if (typeof input === 'object' && input instanceof Buffer) {
|
||||
// input=buffer
|
||||
if (
|
||||
(input.length > 1) &&
|
||||
(input[0] === 0xff && input[1] === 0xd8) || // JPEG
|
||||
(input[0] === 0x89 && input[1] === 0x50) || // PNG
|
||||
(input[0] === 0x52 && input[1] === 0x49) // WebP
|
||||
(input.length > 3) &&
|
||||
// JPEG
|
||||
(input[0] === 0xFF && input[1] === 0xD8) ||
|
||||
// PNG
|
||||
(input[0] === 0x89 && input[1] === 0x50) ||
|
||||
// WebP
|
||||
(input[0] === 0x52 && input[1] === 0x49) ||
|
||||
// TIFF - requires libvips 7.40.0+
|
||||
(semver.gte(libvipsVersion, '7.40.0') && (
|
||||
(input[0] === 0x4D && input[1] === 0x4D && input[2] === 0x00 && (input[3] === 0x2A || input[3] === 0x2B)) ||
|
||||
(input[0] === 0x49 && input[1] === 0x49 && (input[2] === 0x2A || input[2] === 0x2B) && input[3] === 0x00)
|
||||
))
|
||||
) {
|
||||
this.options.bufferIn = input;
|
||||
} else {
|
||||
throw new Error('Buffer contains an unsupported image format. JPEG, PNG and WebP are currently supported.');
|
||||
throw new Error('Buffer contains an unsupported image format. JPEG, PNG, WebP and TIFF are currently supported.');
|
||||
}
|
||||
} else {
|
||||
// input=stream
|
||||
@ -269,6 +280,9 @@ Sharp.prototype.quality = function(quality) {
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
zlib compression level for PNG output
|
||||
*/
|
||||
Sharp.prototype.compressionLevel = function(compressionLevel) {
|
||||
if (!Number.isNaN(compressionLevel) && compressionLevel >= 0 && compressionLevel <= 9) {
|
||||
this.options.compressionLevel = compressionLevel;
|
||||
@ -278,6 +292,18 @@ Sharp.prototype.compressionLevel = function(compressionLevel) {
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Disable the use of adaptive row filtering for PNG output - requires libvips 7.41.0+
|
||||
*/
|
||||
Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) {
|
||||
if (semver.gte(libvipsVersion, '7.41.0')) {
|
||||
this.options.withoutAdaptiveFiltering = (typeof withoutAdaptiveFiltering === 'boolean') ? withoutAdaptiveFiltering : true;
|
||||
} else {
|
||||
console.error('withoutAdaptiveFiltering requires libvips 7.41.0+');
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Sharp.prototype.withMetadata = function(withMetadata) {
|
||||
this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true;
|
||||
return this;
|
||||
@ -506,3 +532,10 @@ module.exports.concurrency = function(concurrency) {
|
||||
module.exports.counters = function() {
|
||||
return sharp.counters();
|
||||
};
|
||||
|
||||
/*
|
||||
Get the version of the libvips library
|
||||
*/
|
||||
module.exports.libvipsVersion = function() {
|
||||
return libvipsVersion;
|
||||
};
|
||||
|
@ -41,9 +41,10 @@
|
||||
"stream"
|
||||
],
|
||||
"dependencies": {
|
||||
"bluebird": "^2.3.10",
|
||||
"bluebird": "^2.3.11",
|
||||
"color": "^0.7.1",
|
||||
"nan": "^1.4.0"
|
||||
"nan": "^1.4.0",
|
||||
"semver": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^2.0.1",
|
||||
|
@ -27,12 +27,25 @@ bool is_tiff(std::string const &str) {
|
||||
return ends_with(str, ".tif") || ends_with(str, ".tiff") || ends_with(str, ".TIF") || ends_with(str, ".TIFF");
|
||||
}
|
||||
|
||||
// Buffer content checkers
|
||||
|
||||
#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 40)
|
||||
static bool buffer_is_tiff(char *buffer, size_t len) {
|
||||
return (
|
||||
len >= 4 && (
|
||||
(buffer[0] == 'M' && buffer[1] == 'M' && buffer[2] == '\0' && (buffer[3] == '*' || buffer[3] == '+')) ||
|
||||
(buffer[0] == 'I' && buffer[1] == 'I' && (buffer[2] == '*' || buffer[2] == '+') && buffer[3] == '\0')
|
||||
)
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
unsigned char const MARKER_JPEG[] = {0xff, 0xd8};
|
||||
unsigned char const MARKER_PNG[] = {0x89, 0x50};
|
||||
unsigned char const MARKER_WEBP[] = {0x52, 0x49};
|
||||
|
||||
/*
|
||||
Initialise a VipsImage from a buffer. Supports JPEG, PNG and WebP.
|
||||
Initialise a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF.
|
||||
Returns the ImageType detected, if any.
|
||||
*/
|
||||
ImageType
|
||||
@ -50,6 +63,12 @@ sharp_init_image_from_buffer(VipsImage **image, void *buffer, size_t const lengt
|
||||
if (!vips_webpload_buffer(buffer, length, image, "access", access, NULL)) {
|
||||
imageType = WEBP;
|
||||
}
|
||||
#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 40)
|
||||
} else if (buffer_is_tiff(static_cast<char*>(buffer), length)) {
|
||||
if (!vips_tiffload_buffer(buffer, length, image, "access", access, NULL)) {
|
||||
imageType = TIFF;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return imageType;
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ struct ResizeBaton {
|
||||
VipsAccess accessMethod;
|
||||
int quality;
|
||||
int compressionLevel;
|
||||
bool withoutAdaptiveFiltering;
|
||||
std::string err;
|
||||
bool withMetadata;
|
||||
|
||||
@ -517,7 +518,8 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
image = colourspaced;
|
||||
}
|
||||
|
||||
// Generate image tile cache when interlace output is required
|
||||
#if !(VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 40 && VIPS_MINOR_VERSION >= 5)
|
||||
// Generate image tile cache when interlace output is required - no longer required as of libvips 7.40.5+
|
||||
if (baton->progressive) {
|
||||
VipsImage *cached = vips_image_new();
|
||||
vips_object_local(hook, cached);
|
||||
@ -527,6 +529,7 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
g_object_unref(image);
|
||||
image = cached;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Output
|
||||
if (baton->output == "__jpeg" || (baton->output == "__input" && inputImageType == JPEG)) {
|
||||
@ -537,11 +540,21 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
}
|
||||
baton->outputFormat = "jpeg";
|
||||
} else if (baton->output == "__png" || (baton->output == "__input" && inputImageType == PNG)) {
|
||||
#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 41)
|
||||
// Select PNG row filter
|
||||
int filter = baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL;
|
||||
// Write PNG to buffer
|
||||
if (vips_pngsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
|
||||
"compression", baton->compressionLevel, "interlace", baton->progressive, "filter", filter, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
#else
|
||||
// Write PNG to buffer
|
||||
if (vips_pngsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
|
||||
"compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
#endif
|
||||
baton->outputFormat = "png";
|
||||
} else if (baton->output == "__webp" || (baton->output == "__input" && inputImageType == WEBP)) {
|
||||
// Write WEBP to buffer
|
||||
@ -564,11 +577,21 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
}
|
||||
baton->outputFormat = "jpeg";
|
||||
} else if (output_png || (match_input && inputImageType == PNG)) {
|
||||
#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 41)
|
||||
// Select PNG row filter
|
||||
int filter = baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL;
|
||||
// Write PNG to file
|
||||
if (vips_pngsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
||||
"compression", baton->compressionLevel, "interlace", baton->progressive, "filter", filter, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
#else
|
||||
// Write PNG to file
|
||||
if (vips_pngsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
||||
"compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
#endif
|
||||
baton->outputFormat = "png";
|
||||
} else if (output_webp || (match_input && inputImageType == WEBP)) {
|
||||
// Write WEBP to file
|
||||
@ -798,6 +821,7 @@ NAN_METHOD(resize) {
|
||||
baton->progressive = options->Get(NanNew<String>("progressive"))->BooleanValue();
|
||||
baton->quality = options->Get(NanNew<String>("quality"))->Int32Value();
|
||||
baton->compressionLevel = options->Get(NanNew<String>("compressionLevel"))->Int32Value();
|
||||
baton->withoutAdaptiveFiltering = options->Get(NanNew<String>("withoutAdaptiveFiltering"))->BooleanValue();
|
||||
baton->withMetadata = options->Get(NanNew<String>("withMetadata"))->BooleanValue();
|
||||
// Output filename or __format for Buffer
|
||||
baton->output = *String::Utf8Value(options->Get(NanNew<String>("output"))->ToString());
|
||||
|
@ -33,6 +33,7 @@ extern "C" void init(Handle<Object> target) {
|
||||
NODE_SET_METHOD(target, "cache", cache);
|
||||
NODE_SET_METHOD(target, "concurrency", concurrency);
|
||||
NODE_SET_METHOD(target, "counters", counters);
|
||||
NODE_SET_METHOD(target, "libvipsVersion", libvipsVersion);
|
||||
}
|
||||
|
||||
NODE_MODULE(sharp, init)
|
||||
|
@ -62,3 +62,13 @@ NAN_METHOD(counters) {
|
||||
counters->Set(NanNew<String>("process"), NanNew<Number>(counter_process));
|
||||
NanReturnValue(counters);
|
||||
}
|
||||
|
||||
/*
|
||||
Get libvips version
|
||||
*/
|
||||
NAN_METHOD(libvipsVersion) {
|
||||
NanScope();
|
||||
char version[9];
|
||||
snprintf(version, 9, "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
|
||||
NanReturnValue(NanNew<String>(version));
|
||||
}
|
||||
|
@ -6,5 +6,6 @@
|
||||
NAN_METHOD(cache);
|
||||
NAN_METHOD(concurrency);
|
||||
NAN_METHOD(counters);
|
||||
NAN_METHOD(libvipsVersion);
|
||||
|
||||
#endif
|
||||
|
@ -9,9 +9,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"imagemagick": "^0.1.3",
|
||||
"imagemagick-native": "^1.4.0",
|
||||
"gm": "^1.16.0",
|
||||
"imagemagick-native": "^1.5.0",
|
||||
"gm": "^1.17.0",
|
||||
"async": "^0.9.0",
|
||||
"semver": "^4.1.0",
|
||||
"benchmark": "^1.0.0"
|
||||
},
|
||||
"license": "Apache 2.0",
|
||||
|
@ -5,6 +5,7 @@ var fs = require('fs');
|
||||
var async = require('async');
|
||||
var assert = require('assert');
|
||||
var Benchmark = require('benchmark');
|
||||
var semver = require('semver');
|
||||
|
||||
var imagemagick = require('imagemagick');
|
||||
var imagemagickNative = require('imagemagick-native');
|
||||
@ -314,7 +315,8 @@ async.series({
|
||||
},
|
||||
png: function(callback) {
|
||||
var inputPngBuffer = fs.readFileSync(fixtures.inputPng);
|
||||
(new Benchmark.Suite('png')).add('imagemagick-file-file', {
|
||||
var pngSuite = new Benchmark.Suite('png');
|
||||
pngSuite.add('imagemagick-file-file', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
imagemagick.resize({
|
||||
@ -422,7 +424,23 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).on('cycle', function(event) {
|
||||
});
|
||||
if (semver.gte(sharp.libvipsVersion(), '7.41.0')) {
|
||||
pngSuite.add('sharp-withoutAdaptiveFiltering', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputPngBuffer).resize(width, height).withoutAdaptiveFiltering().toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
pngSuite.on('cycle', function(event) {
|
||||
console.log(' png ' + String(event.target));
|
||||
}).on('complete', function() {
|
||||
callback(null, this.filter('fastest').pluck('name'));
|
||||
|
@ -3,6 +3,8 @@
|
||||
var fs = require('fs');
|
||||
var assert = require('assert');
|
||||
|
||||
var semver = require('semver');
|
||||
|
||||
var sharp = require('../../index');
|
||||
var fixtures = require('../fixtures');
|
||||
|
||||
@ -343,9 +345,9 @@ describe('Input/output', function() {
|
||||
|
||||
});
|
||||
|
||||
describe('PNG compression level', function() {
|
||||
describe('PNG output', function() {
|
||||
|
||||
it('valid', function(done) {
|
||||
it('compression level is valid', function(done) {
|
||||
var isValid = false;
|
||||
try {
|
||||
sharp().compressionLevel(0);
|
||||
@ -355,7 +357,7 @@ describe('Input/output', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
it('invalid', function(done) {
|
||||
it('compression level is invalid', function(done) {
|
||||
var isValid = false;
|
||||
try {
|
||||
sharp().compressionLevel(-1);
|
||||
@ -365,6 +367,52 @@ describe('Input/output', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
if (semver.gte(sharp.libvipsVersion(), '7.41.0')) {
|
||||
it('withoutAdaptiveFiltering generates smaller file [libvips 7.41.0+]', function(done) {
|
||||
// First generate with adaptive filtering
|
||||
sharp(fixtures.inputPng)
|
||||
.resize(320, 240)
|
||||
.withoutAdaptiveFiltering(false)
|
||||
.toBuffer(function(err, dataAdaptive, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, dataAdaptive.length > 0);
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
// Then generate without
|
||||
sharp(fixtures.inputPng)
|
||||
.resize(320, 240)
|
||||
.withoutAdaptiveFiltering()
|
||||
.toBuffer(function(err, dataWithoutAdaptive, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, dataWithoutAdaptive.length > 0);
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
assert.strictEqual(true, dataWithoutAdaptive.length < dataAdaptive.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if (semver.gte(sharp.libvipsVersion(), '7.40.0')) {
|
||||
it('Load TIFF from Buffer [libvips 7.40.0+]', function(done) {
|
||||
var inputTiffBuffer = fs.readFileSync(fixtures.inputTiff);
|
||||
sharp(inputTiffBuffer)
|
||||
.resize(320, 240)
|
||||
.jpeg()
|
||||
.toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user