mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 18:40:16 +02:00
Add support for gamma correction #43
This commit is contained in:
parent
bac367b005
commit
5728efd32b
10
README.md
10
README.md
@ -279,6 +279,16 @@ Possible interpolators, in order of performance, are:
|
|||||||
* `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" and typically reduces performance by a factor of 2.
|
* `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" and typically reduces performance by a factor of 2.
|
||||||
* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance and typically reduces performance by a factor of 3.
|
* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance and typically reduces performance by a factor of 3.
|
||||||
|
|
||||||
|
#### gamma([gamma])
|
||||||
|
|
||||||
|
Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma` then increasing the encoding (brighten) post-resize at a factor of `gamma`.
|
||||||
|
|
||||||
|
`gamma`, if present, is a Number betweem 1 and 3. The default value is `2.2`, a suitable approximation for sRGB images.
|
||||||
|
|
||||||
|
This can improve the perceived brightness of a resized image in non-linear colour spaces.
|
||||||
|
|
||||||
|
JPEG input images will not take advantage of the shrink-on-load performance optimisation when applying a gamma correction.
|
||||||
|
|
||||||
### Output options
|
### Output options
|
||||||
|
|
||||||
#### jpeg()
|
#### jpeg()
|
||||||
|
17
index.js
17
index.js
@ -20,6 +20,7 @@ var Sharp = function(input) {
|
|||||||
withoutEnlargement: false,
|
withoutEnlargement: false,
|
||||||
sharpen: false,
|
sharpen: false,
|
||||||
interpolator: 'bilinear',
|
interpolator: 'bilinear',
|
||||||
|
gamma: 0,
|
||||||
progressive: false,
|
progressive: false,
|
||||||
sequentialRead: false,
|
sequentialRead: false,
|
||||||
quality: 80,
|
quality: 80,
|
||||||
@ -164,6 +165,22 @@ Sharp.prototype.nohaloInterpolation = util.deprecate(function() {
|
|||||||
return this.interpolateWith(module.exports.interpolator.nohalo);
|
return this.interpolateWith(module.exports.interpolator.nohalo);
|
||||||
}, 'nohaloInterpolation() is deprecated, use interpolateWith(sharp.interpolator.nohalo) instead');
|
}, 'nohaloInterpolation() is deprecated, use interpolateWith(sharp.interpolator.nohalo) instead');
|
||||||
|
|
||||||
|
/*
|
||||||
|
Darken image pre-resize (1/gamma) and brighten post-resize (gamma).
|
||||||
|
Improves brightness of resized image in non-linear colour spaces.
|
||||||
|
*/
|
||||||
|
Sharp.prototype.gamma = function(gamma) {
|
||||||
|
if (typeof gamma === 'undefined') {
|
||||||
|
// Default gamma correction of 2.2 (sRGB)
|
||||||
|
this.options.gamma = 2.2;
|
||||||
|
} else if (!Number.isNaN(gamma) && gamma >= 1 && gamma <= 3) {
|
||||||
|
this.options.gamma = gamma;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid gamma correction (1.0 to 3.0) ' + gamma);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
Sharp.prototype.progressive = function(progressive) {
|
Sharp.prototype.progressive = function(progressive) {
|
||||||
this.options.progressive = (typeof progressive === 'boolean') ? progressive : true;
|
this.options.progressive = (typeof progressive === 'boolean') ? progressive : true;
|
||||||
return this;
|
return this;
|
||||||
|
27
src/sharp.cc
27
src/sharp.cc
@ -26,6 +26,7 @@ struct resize_baton {
|
|||||||
VipsExtend extend;
|
VipsExtend extend;
|
||||||
bool sharpen;
|
bool sharpen;
|
||||||
std::string interpolator;
|
std::string interpolator;
|
||||||
|
double gamma;
|
||||||
bool progressive;
|
bool progressive;
|
||||||
bool without_enlargement;
|
bool without_enlargement;
|
||||||
VipsAccess access_method;
|
VipsAccess access_method;
|
||||||
@ -42,6 +43,7 @@ struct resize_baton {
|
|||||||
gravity(0),
|
gravity(0),
|
||||||
max(false),
|
max(false),
|
||||||
sharpen(false),
|
sharpen(false),
|
||||||
|
gamma(0.0),
|
||||||
progressive(false),
|
progressive(false),
|
||||||
without_enlargement(false),
|
without_enlargement(false),
|
||||||
withMetadata(false) {}
|
withMetadata(false) {}
|
||||||
@ -434,9 +436,9 @@ class ResizeWorker : public NanAsyncWorker {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to use libjpeg shrink-on-load
|
// Try to use libjpeg shrink-on-load, but not when applying gamma correction
|
||||||
int shrink_on_load = 1;
|
int shrink_on_load = 1;
|
||||||
if (inputImageType == JPEG) {
|
if (inputImageType == JPEG && baton->gamma == 0) {
|
||||||
if (shrink >= 8) {
|
if (shrink >= 8) {
|
||||||
factor = factor / 8;
|
factor = factor / 8;
|
||||||
shrink_on_load = 8;
|
shrink_on_load = 8;
|
||||||
@ -469,6 +471,16 @@ class ResizeWorker : public NanAsyncWorker {
|
|||||||
}
|
}
|
||||||
g_object_unref(in);
|
g_object_unref(in);
|
||||||
|
|
||||||
|
// Gamma encoding (darken)
|
||||||
|
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
||||||
|
VipsImage *gamma_encoded = vips_image_new();
|
||||||
|
if (vips_gamma(shrunk_on_load, &gamma_encoded, "exponent", 1.0 / baton->gamma, NULL)) {
|
||||||
|
return resize_error(baton, shrunk_on_load);
|
||||||
|
}
|
||||||
|
g_object_unref(shrunk_on_load);
|
||||||
|
shrunk_on_load = gamma_encoded;
|
||||||
|
}
|
||||||
|
|
||||||
VipsImage *shrunk = vips_image_new();
|
VipsImage *shrunk = vips_image_new();
|
||||||
if (shrink > 1) {
|
if (shrink > 1) {
|
||||||
// Use vips_shrink with the integral reduction
|
// Use vips_shrink with the integral reduction
|
||||||
@ -567,6 +579,16 @@ class ResizeWorker : public NanAsyncWorker {
|
|||||||
}
|
}
|
||||||
g_object_unref(canvased);
|
g_object_unref(canvased);
|
||||||
|
|
||||||
|
// Gamma decoding (brighten)
|
||||||
|
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
||||||
|
VipsImage *gamma_decoded = vips_image_new();
|
||||||
|
if (vips_gamma(sharpened, &gamma_decoded, "exponent", baton->gamma, NULL)) {
|
||||||
|
return resize_error(baton, sharpened);
|
||||||
|
}
|
||||||
|
g_object_unref(sharpened);
|
||||||
|
sharpened = gamma_decoded;
|
||||||
|
}
|
||||||
|
|
||||||
// Always convert to sRGB colour space
|
// Always convert to sRGB colour space
|
||||||
VipsImage *colourspaced = vips_image_new();
|
VipsImage *colourspaced = vips_image_new();
|
||||||
vips_colourspace(sharpened, &colourspaced, VIPS_INTERPRETATION_sRGB, NULL);
|
vips_colourspace(sharpened, &colourspaced, VIPS_INTERPRETATION_sRGB, NULL);
|
||||||
@ -702,6 +724,7 @@ NAN_METHOD(resize) {
|
|||||||
baton->gravity = options->Get(NanNew<String>("gravity"))->Int32Value();
|
baton->gravity = options->Get(NanNew<String>("gravity"))->Int32Value();
|
||||||
baton->sharpen = options->Get(NanNew<String>("sharpen"))->BooleanValue();
|
baton->sharpen = options->Get(NanNew<String>("sharpen"))->BooleanValue();
|
||||||
baton->interpolator = *String::Utf8Value(options->Get(NanNew<String>("interpolator"))->ToString());
|
baton->interpolator = *String::Utf8Value(options->Get(NanNew<String>("interpolator"))->ToString());
|
||||||
|
baton->gamma = options->Get(NanNew<String>("gamma"))->NumberValue();
|
||||||
baton->progressive = options->Get(NanNew<String>("progressive"))->BooleanValue();
|
baton->progressive = options->Get(NanNew<String>("progressive"))->BooleanValue();
|
||||||
baton->without_enlargement = options->Get(NanNew<String>("withoutEnlargement"))->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;
|
baton->access_method = options->Get(NanNew<String>("sequentialRead"))->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
||||||
|
BIN
tests/fixtures/gamma_dalai_lama_gray.jpg
vendored
Normal file
BIN
tests/fixtures/gamma_dalai_lama_gray.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 83 KiB |
@ -245,6 +245,18 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}).add("sharp-file-buffer-gamma", {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpg).resize(width, height).gamma().toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}).add("sharp-file-buffer-progressive", {
|
}).add("sharp-file-buffer-progressive", {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
|
@ -18,6 +18,8 @@ var outputTiff = path.join(fixturesPath, "output.tiff");
|
|||||||
|
|
||||||
var inputJpgWithExif = path.join(fixturesPath, "Landscape_8.jpg"); // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_8.jpg
|
var inputJpgWithExif = path.join(fixturesPath, "Landscape_8.jpg"); // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_8.jpg
|
||||||
|
|
||||||
|
var inputJpgWithGammaHoliness = path.join(fixturesPath, "gamma_dalai_lama_gray.jpg"); // http://www.4p8.com/eric.brasseur/gamma.html
|
||||||
|
|
||||||
var inputPng = path.join(fixturesPath, "50020484-00001.png"); // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
|
var inputPng = path.join(fixturesPath, "50020484-00001.png"); // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
|
||||||
var inputWebp = path.join(fixturesPath, "4.webp"); // http://www.gstatic.com/webp/gallery/4.webp
|
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 inputGif = path.join(fixturesPath, "Crash_test.gif"); // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
|
||||||
@ -571,6 +573,25 @@ async.series([
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
// Gamma correction
|
||||||
|
function(done) {
|
||||||
|
sharp(inputJpgWithGammaHoliness).resize(129, 111).toFile(path.join(fixturesPath, 'output.gamma-0.0.jpg'), function(err) {
|
||||||
|
if (err) throw err;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(done) {
|
||||||
|
sharp(inputJpgWithGammaHoliness).resize(129, 111).gamma().toFile(path.join(fixturesPath, 'output.gamma-2.2.jpg'), function(err) {
|
||||||
|
if (err) throw err;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(done) {
|
||||||
|
sharp(inputJpgWithGammaHoliness).resize(129, 111).gamma(3).toFile(path.join(fixturesPath, 'output.gamma-3.0.jpg'), function(err) {
|
||||||
|
if (err) throw err;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
},
|
||||||
// Verify internal counters
|
// Verify internal counters
|
||||||
function(done) {
|
function(done) {
|
||||||
var counters = sharp.counters();
|
var counters = sharp.counters();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user