diff --git a/docs/src/content/docs/api-output.md b/docs/src/content/docs/api-output.md index 8368e54d..5acd2fad 100644 --- a/docs/src/content/docs/api-output.md +++ b/docs/src/content/docs/api-output.md @@ -201,7 +201,7 @@ const dataWithMergedExif = await sharp(inputWithExif) Keep ICC profile from the input image in the output image. -For non-RGB output use [toColourspace](/api-colour/#tocolourspace). +When input and output colour spaces differ, use with [toColourspace](/api-colour/#tocolourspace) and optionally [pipelineColourspace](/api-colour/#pipelinecolourspace). **Since**: 0.33.0 @@ -211,6 +211,14 @@ const outputWithIccProfile = await sharp(inputWithIccProfile) .keepIccProfile() .toBuffer(); ``` +**Example** +```js +const cmykOutputWithIccProfile = await sharp(cmykInputWithIccProfile) + .pipelineColourspace('cmyk') + .toColourspace('cmyk') + .keepIccProfile() + .toBuffer(); +``` ## withIccProfile diff --git a/docs/src/content/docs/changelog/v0.34.5.md b/docs/src/content/docs/changelog/v0.34.5.md index 8cfca9c9..df994024 100644 --- a/docs/src/content/docs/changelog/v0.34.5.md +++ b/docs/src/content/docs/changelog/v0.34.5.md @@ -16,3 +16,6 @@ slug: changelog/v0.34.5 * Improve error messaging when only warnings issued. [#4465](https://github.com/lovell/sharp/issues/4465) + +* Simplify ICC processing when retaining input profiles. + [#4468](https://github.com/lovell/sharp/issues/4468) diff --git a/lib/output.js b/lib/output.js index dac7774c..27a6ac47 100644 --- a/lib/output.js +++ b/lib/output.js @@ -256,7 +256,7 @@ function withExifMerge (exif) { /** * Keep ICC profile from the input image in the output image. * - * For non-RGB output use {@link /api-colour/#tocolourspace toColourspace}. + * When input and output colour spaces differ, use with {@link /api-colour/#tocolourspace toColourspace} and optionally {@link /api-colour/#pipelinecolourspace pipelineColourspace}. * * @since 0.33.0 * @@ -265,6 +265,13 @@ function withExifMerge (exif) { * .keepIccProfile() * .toBuffer(); * + * @example + * const cmykOutputWithIccProfile = await sharp(cmykInputWithIccProfile) + * .pipelineColourspace('cmyk') + * .toColourspace('cmyk') + * .keepIccProfile() + * .toBuffer(); + * * @returns {Sharp} */ function keepIccProfile () { diff --git a/src/pipeline.cc b/src/pipeline.cc index 5da169a1..23cad078 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -797,20 +797,14 @@ class PipelineWorker : public Napi::AsyncWorker { image = sharp::EnsureAlpha(image, baton->ensureAlpha); } - // Convert image to sRGB, if not already + // Ensure output colour space if (sharp::Is16Bit(image.interpretation())) { image = image.cast(VIPS_FORMAT_USHORT); } if (image.interpretation() != baton->colourspace) { - // 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->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK && - baton->withIccProfile.empty() && sharp::HasProfile(image)) { - image = image.icc_transform(processingProfile, VImage::option() - ->set("embedded", true) - ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8) - ->set("intent", VIPS_INTENT_PERCEPTUAL)); + if (inputProfile.first && baton->withIccProfile.empty()) { + image = sharp::SetProfile(image, inputProfile); } } @@ -845,8 +839,6 @@ class PipelineWorker : public Napi::AsyncWorker { } catch(...) { sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid profile", nullptr); } - } else if (baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) { - image = sharp::SetProfile(image, inputProfile); } // Negate the colours in the image diff --git a/test/unit/metadata.js b/test/unit/metadata.js index 0e7b4747..300380ed 100644 --- a/test/unit/metadata.js +++ b/test/unit/metadata.js @@ -651,16 +651,27 @@ describe('Image metadata', () => { assert.strictEqual(description, 'Generic RGB Profile'); }); - it('keep existing ICC profile, ignore colourspace conversion', async () => { + it('keep existing CMYK input profile for CMYK output', async () => { + const data = await sharp(fixtures.inputJpgWithCmykProfile) + .keepIccProfile() + .toColourspace('cmyk') + .toBuffer(); + + const metadata = await sharp(data).metadata(); + assert.strictEqual(metadata.channels, 4); + const { description } = icc.parse(metadata.icc); + assert.strictEqual(description, 'U.S. Web Coated (SWOP) v2'); + }); + + it('transform with but discard existing RGB input profile for CMYK output', async () => { const data = await sharp(fixtures.inputJpgWithExif) .keepIccProfile() .toColourspace('cmyk') .toBuffer(); const metadata = await sharp(data).metadata(); - assert.strictEqual(metadata.channels, 3); - const { description } = icc.parse(metadata.icc); - assert.strictEqual(description, 'Generic RGB Profile'); + assert.strictEqual(metadata.channels, 4); + assert.strictEqual(metadata.icc, undefined); }); it('keep existing ICC profile, avoid colour transform', async () => {