Add support for greyscale conversion #43

This commit is contained in:
Lovell Fuller 2014-09-16 11:22:26 +01:00
parent d41321254a
commit 1c79d6fb5d
5 changed files with 84 additions and 8 deletions

View File

@ -283,7 +283,7 @@ This is equivalent to GraphicsMagick's `>` geometry option: "change the dimensio
#### sharpen()
Perform a mild sharpen of the resultant image. This typically reduces performance by 30%.
Perform a mild sharpen of the output image. This typically reduces performance by 10%.
#### interpolateWith(interpolator)
@ -308,6 +308,14 @@ This can improve the perceived brightness of a resized image in non-linear colou
JPEG input images will not take advantage of the shrink-on-load performance optimisation when applying a gamma correction.
#### grayscale() / greyscale()
Convert to 8-bit greyscale; 256 shades of grey.
This is a linear operation. If the input image is in a non-linear colourspace such as sRGB, use `gamma()` with `greyscale()` for the best results.
The output image will still be web-friendly sRGB and contain three (identical) channels.
### Output options
#### jpeg()
@ -332,7 +340,7 @@ The output quality to use for lossy JPEG, WebP and TIFF output formats. The defa
Use progressive (interlace) scan for JPEG and PNG output. This typically reduces compression performance by 30% but results in an image that can be rendered sooner when decompressed.
#### withMetadata([boolean])
#### withMetadata()
Include all metadata (ICC, EXIF, XMP) from the input image in the output image. The default behaviour is to strip all metadata.

View File

@ -21,6 +21,7 @@ var Sharp = function(input) {
sharpen: false,
interpolator: 'bilinear',
gamma: 0,
greyscale: false,
progressive: false,
sequentialRead: false,
quality: 80,
@ -181,6 +182,15 @@ Sharp.prototype.gamma = function(gamma) {
return this;
};
/*
Convert to greyscale
*/
Sharp.prototype.greyscale = function(greyscale) {
this.options.greyscale = (typeof greyscale === 'boolean') ? greyscale : true;
return this;
};
Sharp.prototype.grayscale = Sharp.prototype.greyscale;
Sharp.prototype.progressive = function(progressive) {
this.options.progressive = (typeof progressive === 'boolean') ? progressive : true;
return this;

View File

@ -28,6 +28,7 @@ struct resize_baton {
bool sharpen;
std::string interpolator;
double gamma;
bool greyscale;
bool progressive;
bool without_enlargement;
VipsAccess access_method;
@ -483,6 +484,16 @@ class ResizeWorker : public NanAsyncWorker {
shrunk_on_load = gamma_encoded;
}
// Convert to greyscale (linear, therefore after gamma encoding, if any)
if (baton->greyscale) {
VipsImage *greyscale = vips_image_new();
if (vips_colourspace(shrunk_on_load, &greyscale, VIPS_INTERPRETATION_B_W, NULL)) {
return resize_error(baton, shrunk_on_load);
}
g_object_unref(shrunk_on_load);
shrunk_on_load = greyscale;
}
VipsImage *shrunk = vips_image_new();
if (shrink > 1) {
// Use vips_shrink with the integral reduction
@ -750,6 +761,7 @@ NAN_METHOD(resize) {
baton->sharpen = options->Get(NanNew<String>("sharpen"))->BooleanValue();
baton->interpolator = *String::Utf8Value(options->Get(NanNew<String>("interpolator"))->ToString());
baton->gamma = options->Get(NanNew<String>("gamma"))->NumberValue();
baton->greyscale = options->Get(NanNew<String>("greyscale"))->BooleanValue();
baton->progressive = options->Get(NanNew<String>("progressive"))->BooleanValue();
baton->without_enlargement = options->Get(NanNew<String>("withoutEnlargement"))->BooleanValue();
baton->access_method = options->Get(NanNew<String>("sequentialRead"))->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;

View File

@ -257,6 +257,30 @@ async.series({
}
});
}
}).add("sharp-file-buffer-greyscale", {
defer: true,
fn: function(deferred) {
sharp(inputJpg).resize(width, height).greyscale().toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add("sharp-file-buffer-greyscale-gamma", {
defer: true,
fn: function(deferred) {
sharp(inputJpg).resize(width, height).gamma().greyscale().toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add("sharp-file-buffer-progressive", {
defer: true,
fn: function(deferred) {

View File

@ -24,6 +24,8 @@ var inputPng = path.join(fixturesPath, "50020484-00001.png"); // http://c.searsp
var inputWebP = path.join(fixturesPath, "4.webp"); // http://www.gstatic.com/webp/gallery/4.webp
var inputGif = path.join(fixturesPath, "Crash_test.gif"); // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
var outputZoinks = path.join(fixturesPath, 'output.zoinks'); // an "unknown" file extension
// Ensure cache limits can be set
sharp.cache(0); // Disable
sharp.cache(50, 500); // 50MB, 500 items
@ -425,6 +427,7 @@ async.series([
anErrorWasEmitted = !!err;
}).on('end', function() {
assert(anErrorWasEmitted);
fs.unlinkSync(outputJpg);
done();
});
var readableButNotAnImage = fs.createReadStream(__filename);
@ -439,6 +442,7 @@ async.series([
anErrorWasEmitted = !!err;
}).on('end', function() {
assert(anErrorWasEmitted);
fs.unlinkSync(outputJpg);
done();
});
var writable = fs.createWriteStream(outputJpg);
@ -522,44 +526,49 @@ async.series([
},
// Output filename without extension should mirror input format
function(done) {
sharp(inputJpg).resize(320, 80).toFile(path.join(fixturesPath, 'output.zoinks'), function(err, info) {
sharp(inputJpg).resize(320, 80).toFile(outputZoinks, function(err, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
fs.unlinkSync(outputZoinks);
done();
});
},
function(done) {
sharp(inputPng).resize(320, 80).toFile(path.join(fixturesPath, 'output.zoinks'), function(err, info) {
sharp(inputPng).resize(320, 80).toFile(outputZoinks, function(err, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
fs.unlinkSync(outputZoinks);
done();
});
},
function(done) {
sharp(inputWebP).resize(320, 80).toFile(path.join(fixturesPath, 'output.zoinks'), function(err, info) {
sharp(inputWebP).resize(320, 80).toFile(outputZoinks, function(err, info) {
if (err) throw err;
assert.strictEqual('webp', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
fs.unlinkSync(outputZoinks);
done();
});
},
function(done) {
sharp(inputTiff).resize(320, 80).toFile(path.join(fixturesPath, 'output.zoinks'), function(err, info) {
sharp(inputTiff).resize(320, 80).toFile(outputZoinks, function(err, info) {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height);
fs.unlinkSync(outputZoinks);
done();
});
},
function(done) {
sharp(inputGif).resize(320, 80).toFile(path.join(fixturesPath, 'output.zoinks'), function(err, info) {
sharp(inputGif).resize(320, 80).toFile(outputZoinks, function(err, info) {
assert(!!err);
done();
});
},
// Metadata - JPEG
@ -681,7 +690,7 @@ async.series([
},
// Gamma correction
function(done) {
sharp(inputJpgWithGammaHoliness).resize(129, 111).toFile(path.join(fixturesPath, 'output.gamma-0.0.jpg'), function(err) {
sharp(inputJpgWithGammaHoliness).resize(129, 111).toFile(path.join(fixturesPath, 'output.gamma-0.0.jpg'), function(err, info) {
if (err) throw err;
done();
});
@ -698,6 +707,19 @@ async.series([
done();
});
},
// Greyscale conversion
function(done) {
sharp(inputJpg).resize(320, 240).greyscale().toFile(path.join(fixturesPath, 'output.greyscale-gamma-0.0.jpg'), function(err, info) {
if (err) throw err;
done();
});
},
function(done) {
sharp(inputJpg).resize(320, 240).gamma().greyscale().toFile(path.join(fixturesPath, 'output.greyscale-gamma-2.2.jpg'), function(err) {
if (err) throw err;
done();
});
},
// Verify internal counters
function(done) {
var counters = sharp.counters();