From 104464c2e00cf362992fa6170d4525ebcd1dcabe Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Mon, 30 Aug 2021 17:15:17 +0100 Subject: [PATCH] Ensure images with P3 profiles retain full gamut #2862 --- docs/changelog.md | 3 +++ src/pipeline.cc | 17 ++++++++++------- test/fixtures/index.js | 1 + test/fixtures/p3.png | Bin 0 -> 610 bytes test/unit/colourspace.js | 19 +++++++++++++++++++ 5 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 test/fixtures/p3.png diff --git a/docs/changelog.md b/docs/changelog.md index 6f31f21b..db0165b0 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -12,6 +12,9 @@ Requires libvips v8.11.3 * Ensure background is always premultiplied when compositing. [#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 * Drop support for Node.js 10, now requires Node.js >= 12.13.0. diff --git a/src/pipeline.cc b/src/pipeline.cc index 7a154d76..149a6e01 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -291,14 +291,15 @@ class PipelineWorker : public Napi::AsyncWorker { } // Ensure we're using a device-independent colour space + char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb"; if ( sharp::HasProfile(image) && image.interpretation() != VIPS_INTERPRETATION_LABS && image.interpretation() != VIPS_INTERPRETATION_GREY16 ) { - // Convert to sRGB using embedded profile + // Convert to sRGB/P3 using embedded profile try { - image = image.icc_transform("srgb", VImage::option() + image = image.icc_transform(processingProfile, VImage::option() ->set("embedded", TRUE) ->set("depth", image.interpretation() == VIPS_INTERPRETATION_RGB16 ? 16 : 8) ->set("intent", VIPS_INTENT_PERCEPTUAL)); @@ -306,7 +307,7 @@ class PipelineWorker : public Napi::AsyncWorker { // Ignore failure of embedded profile } } 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("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 image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation())); // Transform colours from embedded profile to output profile - if (baton->withMetadata && sharp::HasProfile(image)) { - image = image.icc_transform(vips_enum_nick(VIPS_TYPE_INTERPRETATION, baton->colourspace), - VImage::option()->set("embedded", TRUE)); + if (baton->withMetadata && sharp::HasProfile(image) && baton->withMetadataIcc.empty()) { + image = image.icc_transform("srgb", VImage::option() + ->set("embedded", TRUE) + ->set("intent", VIPS_INTENT_PERCEPTUAL)); } } @@ -726,7 +728,8 @@ class PipelineWorker : public Napi::AsyncWorker { image = image.icc_transform( const_cast(baton->withMetadataIcc.data()), VImage::option() - ->set("input_profile", "srgb") + ->set("input_profile", processingProfile) + ->set("embedded", TRUE) ->set("intent", VIPS_INTENT_PERCEPTUAL)); } // Override EXIF Orientation tag diff --git a/test/fixtures/index.js b/test/fixtures/index.js index 505f8ca8..9e8b019a 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -92,6 +92,7 @@ module.exports = { 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 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 inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp diff --git a/test/fixtures/p3.png b/test/fixtures/p3.png new file mode 100644 index 0000000000000000000000000000000000000000..96e10ff1587c41d25b36930b722c91448e29a7f8 GIT binary patch literal 610 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9Sn@U<}H1b`Ho)&ShZGn3$Z95ag+& zaUwA>A>qVXZB4GC@}Q26o}h&N=MNk`aQwjO0|ygk&WMS5AR+L8Iph$-;)x8(%!jvU zD@1_m5O#s+h4 zL{5m9!XcK#mb7e(30s^0fe(*(6A}dO9XoK~=W>g)H#l_8pLl%az=5jw|No!Q;^BES zSyJMk-;e+Qzb^uss>hbK@9=>F1%3w42kCQ8v-j))2C_o3N02WAL+%U)hT?t(Mh;U3 zhPo^UhLn>GjGP-87z_&;7=-r-{pY_9RLhp+?e4pRt%(#z_{*( zizG;}z#|eU$Sn-Qj105pNB{)|k`w*PfHXLI!K#6}7T$idAINt1ba4!kxLkV9QK-Ry zgXJLK!zcgkU$OU0KXOUrT<_Y{1#@{OHy&uT+q^+ATH<}K$Cgza+H`vOBk%Mq&2Rme i{GwIv(R<4s1<0WRI8%W}GI+ZBxvX { + 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 () { assert.throws(function () { sharp(fixtures.inputJpg)