Ensure images with P3 profiles retain full gamut #2862

This commit is contained in:
Lovell Fuller 2021-08-30 17:15:17 +01:00
parent 60adc110f5
commit 104464c2e0
5 changed files with 33 additions and 7 deletions

View File

@ -12,6 +12,9 @@ Requires libvips v8.11.3
* Ensure background is always premultiplied when compositing. * Ensure background is always premultiplied when compositing.
[#2858](https://github.com/lovell/sharp/issues/2858) [#2858](https://github.com/lovell/sharp/issues/2858)
* Ensure images with P3 profiles retain full gamut.
[#2862](https://github.com/lovell/sharp/issues/2862)
### v0.29.0 - 17th August 2021 ### v0.29.0 - 17th August 2021
* Drop support for Node.js 10, now requires Node.js >= 12.13.0. * Drop support for Node.js 10, now requires Node.js >= 12.13.0.

View File

@ -291,14 +291,15 @@ class PipelineWorker : public Napi::AsyncWorker {
} }
// Ensure we're using a device-independent colour space // Ensure we're using a device-independent colour space
char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb";
if ( if (
sharp::HasProfile(image) && sharp::HasProfile(image) &&
image.interpretation() != VIPS_INTERPRETATION_LABS && image.interpretation() != VIPS_INTERPRETATION_LABS &&
image.interpretation() != VIPS_INTERPRETATION_GREY16 image.interpretation() != VIPS_INTERPRETATION_GREY16
) { ) {
// Convert to sRGB using embedded profile // Convert to sRGB/P3 using embedded profile
try { try {
image = image.icc_transform("srgb", VImage::option() image = image.icc_transform(processingProfile, VImage::option()
->set("embedded", TRUE) ->set("embedded", TRUE)
->set("depth", image.interpretation() == VIPS_INTERPRETATION_RGB16 ? 16 : 8) ->set("depth", image.interpretation() == VIPS_INTERPRETATION_RGB16 ? 16 : 8)
->set("intent", VIPS_INTENT_PERCEPTUAL)); ->set("intent", VIPS_INTENT_PERCEPTUAL));
@ -306,7 +307,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Ignore failure of embedded profile // Ignore failure of embedded profile
} }
} else if (image.interpretation() == VIPS_INTERPRETATION_CMYK) { } else if (image.interpretation() == VIPS_INTERPRETATION_CMYK) {
image = image.icc_transform("srgb", VImage::option() image = image.icc_transform(processingProfile, VImage::option()
->set("input_profile", "cmyk") ->set("input_profile", "cmyk")
->set("intent", VIPS_INTENT_PERCEPTUAL)); ->set("intent", VIPS_INTENT_PERCEPTUAL));
} }
@ -715,9 +716,10 @@ class PipelineWorker : public Napi::AsyncWorker {
// Convert colourspace, pass the current known interpretation so libvips doesn't have to guess // Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation())); image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
// Transform colours from embedded profile to output profile // Transform colours from embedded profile to output profile
if (baton->withMetadata && sharp::HasProfile(image)) { if (baton->withMetadata && sharp::HasProfile(image) && baton->withMetadataIcc.empty()) {
image = image.icc_transform(vips_enum_nick(VIPS_TYPE_INTERPRETATION, baton->colourspace), image = image.icc_transform("srgb", VImage::option()
VImage::option()->set("embedded", TRUE)); ->set("embedded", TRUE)
->set("intent", VIPS_INTENT_PERCEPTUAL));
} }
} }
@ -726,7 +728,8 @@ class PipelineWorker : public Napi::AsyncWorker {
image = image.icc_transform( image = image.icc_transform(
const_cast<char*>(baton->withMetadataIcc.data()), const_cast<char*>(baton->withMetadataIcc.data()),
VImage::option() VImage::option()
->set("input_profile", "srgb") ->set("input_profile", processingProfile)
->set("embedded", TRUE)
->set("intent", VIPS_INTENT_PERCEPTUAL)); ->set("intent", VIPS_INTENT_PERCEPTUAL));
} }
// Override EXIF Orientation tag // Override EXIF Orientation tag

View File

@ -92,6 +92,7 @@ module.exports = {
inputPngRGBWithAlpha: getPath('2569067123_aca715a2ee_o.png'), // http://www.flickr.com/photos/grizdave/2569067123/ (same as inputJpg) inputPngRGBWithAlpha: getPath('2569067123_aca715a2ee_o.png'), // http://www.flickr.com/photos/grizdave/2569067123/ (same as inputJpg)
inputPngImageInAlpha: getPath('image-in-alpha.png'), // https://github.com/lovell/sharp/issues/1597 inputPngImageInAlpha: getPath('image-in-alpha.png'), // https://github.com/lovell/sharp/issues/1597
inputPngSolidAlpha: getPath('with-alpha.png'), // https://github.com/lovell/sharp/issues/1599 inputPngSolidAlpha: getPath('with-alpha.png'), // https://github.com/lovell/sharp/issues/1599
inputPngP3: getPath('p3.png'), // https://github.com/lovell/sharp/issues/2862
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp

BIN
test/fixtures/p3.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

View File

@ -105,6 +105,25 @@ describe('Colour space conversion', function () {
}); });
}); });
it('Convert P3 to sRGB', async () => {
const [r, g, b] = await sharp(fixtures.inputPngP3)
.raw()
.toBuffer();
assert.strictEqual(r, 255);
assert.strictEqual(g, 0);
assert.strictEqual(b, 0);
});
it('Passthrough P3', async () => {
const [r, g, b] = await sharp(fixtures.inputPngP3)
.withMetadata({ icc: 'p3' })
.raw()
.toBuffer();
assert.strictEqual(r, 234);
assert.strictEqual(g, 51);
assert.strictEqual(b, 34);
});
it('Invalid pipelineColourspace input', function () { it('Invalid pipelineColourspace input', function () {
assert.throws(function () { assert.throws(function () {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)