Skip to content

Commit

Permalink
Add chroma subsampling options for JPEG output
Browse files Browse the repository at this point in the history
  • Loading branch information
lovell committed Feb 13, 2015
1 parent 0e91ca9 commit 1f7e80e
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 5 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,15 @@ Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.

The default behaviour is to strip all metadata and convert to the device-independent sRGB colour space.

#### withoutChromaSubsampling()

Disable the use of [chroma subsampling](http://en.wikipedia.org/wiki/Chroma_subsampling) with JPEG output (4:4:4).

This can improve colour representation at higher quality settings (90+),
but usually increases output file size and typically reduces performance by 25%.

The default behaviour is to use chroma subsampling (4:2:0).

#### compressionLevel(compressionLevel)

An advanced setting for the _zlib_ compression level of the lossless PNG output format. The default level is `6`.
Expand Down
9 changes: 9 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ var Sharp = function(input) {
quality: 80,
compressionLevel: 6,
withoutAdaptiveFiltering: false,
withoutChromaSubsampling: false,
streamOut: false,
withMetadata: false
};
Expand Down Expand Up @@ -368,6 +369,14 @@ Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) {
return this;
};

/*
Disable the use of chroma subsampling for JPEG output
*/
Sharp.prototype.withoutChromaSubsampling = function(withoutChromaSubsampling) {
this.options.withoutChromaSubsampling = (typeof withoutChromaSubsampling === 'boolean') ? withoutChromaSubsampling : true;
return this;
};

Sharp.prototype.withMetadata = function(withMetadata) {
this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true;
return this;
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@
"vips"
],
"dependencies": {
"bluebird": "^2.9.8",
"bluebird": "^2.9.9",
"color": "^0.7.3",
"nan": "^1.6.2",
"semver": "^4.2.2"
"semver": "^4.3.0"
},
"devDependencies": {
"mocha": "^2.1.0",
Expand Down
9 changes: 7 additions & 2 deletions src/resize.cc
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ struct ResizeBaton {
int quality;
int compressionLevel;
bool withoutAdaptiveFiltering;
bool withoutChromaSubsampling;
std::string err;
bool withMetadata;

Expand Down Expand Up @@ -117,6 +118,7 @@ struct ResizeBaton {
quality(80),
compressionLevel(6),
withoutAdaptiveFiltering(false),
withoutChromaSubsampling(false),
withMetadata(false) {
background[0] = 0.0;
background[1] = 0.0;
Expand Down Expand Up @@ -675,7 +677,8 @@ class ResizeWorker : public NanAsyncWorker {
if (baton->output == "__jpeg" || (baton->output == "__input" && inputImageType == ImageType::JPEG)) {
// Write JPEG to buffer
if (vips_jpegsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
"Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
"Q", baton->quality, "optimize_coding", TRUE, "no_subsample", baton->withoutChromaSubsampling,
"interlace", baton->progressive, NULL)) {
return Error(baton, hook);
}
baton->outputFormat = "jpeg";
Expand Down Expand Up @@ -741,7 +744,8 @@ class ResizeWorker : public NanAsyncWorker {
if (outputJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
// Write JPEG to file
if (vips_jpegsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
"Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
"Q", baton->quality, "optimize_coding", TRUE, "no_subsample", baton->withoutChromaSubsampling,
"interlace", baton->progressive, NULL)) {
return Error(baton, hook);
}
baton->outputFormat = "jpeg";
Expand Down Expand Up @@ -992,6 +996,7 @@ NAN_METHOD(resize) {
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->withoutChromaSubsampling = options->Get(NanNew<String>("withoutChromaSubsampling"))->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());
Expand Down
2 changes: 1 addition & 1 deletion test/bench/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"imagemagick-native": "^1.7.0",
"gm": "^1.17.0",
"async": "^0.9.0",
"semver": "^4.2.0",
"semver": "^4.3.0",
"benchmark": "^1.0.0"
},
"license": "Apache 2.0",
Expand Down
12 changes: 12 additions & 0 deletions test/bench/perf.js
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,18 @@ async.series({
}
});
}
}).add('sharp-without-chroma-subsampling', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer).resize(width, height).withoutChromaSubsampling().toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-rotate', {
defer: true,
fn: function(deferred) {
Expand Down
29 changes: 29 additions & 0 deletions test/unit/io.js
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,35 @@ describe('Input/output', function() {

});

it('Without chroma subsampling generates larger file', function(done) {
// First generate with chroma subsampling (default)
sharp(fixtures.inputJpg)
.resize(320, 240)
.withoutChromaSubsampling(false)
.toBuffer(function(err, withChromaSubsamplingData, withChromaSubsamplingInfo) {
if (err) throw err;
assert.strictEqual(true, withChromaSubsamplingData.length > 0);
assert.strictEqual(withChromaSubsamplingData.length, withChromaSubsamplingInfo.size);
assert.strictEqual('jpeg', withChromaSubsamplingInfo.format);
assert.strictEqual(320, withChromaSubsamplingInfo.width);
assert.strictEqual(240, withChromaSubsamplingInfo.height);
// Then generate without
sharp(fixtures.inputJpg)
.resize(320, 240)
.withoutChromaSubsampling()
.toBuffer(function(err, withoutChromaSubsamplingData, withoutChromaSubsamplingInfo) {
if (err) throw err;
assert.strictEqual(true, withoutChromaSubsamplingData.length > 0);
assert.strictEqual(withoutChromaSubsamplingData.length, withoutChromaSubsamplingInfo.size);
assert.strictEqual('jpeg', withoutChromaSubsamplingInfo.format);
assert.strictEqual(320, withoutChromaSubsamplingInfo.width);
assert.strictEqual(240, withoutChromaSubsamplingInfo.height);
assert.strictEqual(true, withChromaSubsamplingData.length < withoutChromaSubsamplingData.length);
done();
});
});
});

it('Convert SVG, if supported, to PNG', function(done) {
sharp(fixtures.inputSvg)
.resize(100, 100)
Expand Down

0 comments on commit 1f7e80e

Please sign in to comment.