diff --git a/docs/changelog.md b/docs/changelog.md index 735fc3665..1c95c22ca 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -9,7 +9,10 @@ Requires libvips v8.14.4 * Ensure composite tile images are fully decoded (regression in 0.32.0). [#3767](https://github.com/lovell/sharp/issues/3767) -* Ensure `withMetadata` does not add default sRGB profile to RGB16 (regression in 0.32.5). +* Ensure `withMetadata` can add ICC profiles to RGB16 output. + [#3773](https://github.com/lovell/sharp/issues/3773) + +* Ensure `withMetadata` does not reduce 16-bit images to 8-bit (regression in 0.32.5). [#3773](https://github.com/lovell/sharp/issues/3773) ### v0.32.5 - 15th August 2023 diff --git a/src/pipeline.cc b/src/pipeline.cc index 74db3f49f..5596d86fc 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -326,7 +326,7 @@ class PipelineWorker : public Napi::AsyncWorker { try { image = image.icc_transform(processingProfile, VImage::option() ->set("embedded", TRUE) - ->set("depth", image.interpretation() == VIPS_INTERPRETATION_RGB16 ? 16 : 8) + ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8) ->set("intent", VIPS_INTENT_PERCEPTUAL)); } catch(...) { sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid embedded profile", nullptr); @@ -763,6 +763,7 @@ class PipelineWorker : public Napi::AsyncWorker { if (baton->withMetadata && sharp::HasProfile(image) && baton->withMetadataIcc.empty()) { image = image.icc_transform("srgb", VImage::option() ->set("embedded", TRUE) + ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8) ->set("intent", VIPS_INTENT_PERCEPTUAL)); } } @@ -789,14 +790,13 @@ class PipelineWorker : public Napi::AsyncWorker { // Apply output ICC profile if (baton->withMetadata) { - if (image.interpretation() == VIPS_INTERPRETATION_sRGB || !baton->withMetadataIcc.empty()) { - image = image.icc_transform( - baton->withMetadataIcc.empty() ? "srgb" : const_cast(baton->withMetadataIcc.data()), - VImage::option() - ->set("input_profile", processingProfile) - ->set("embedded", TRUE) - ->set("intent", VIPS_INTENT_PERCEPTUAL)); - } + image = image.icc_transform( + baton->withMetadataIcc.empty() ? "srgb" : const_cast(baton->withMetadataIcc.data()), + VImage::option() + ->set("input_profile", processingProfile) + ->set("embedded", TRUE) + ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8) + ->set("intent", VIPS_INTENT_PERCEPTUAL)); } // Override EXIF Orientation tag if (baton->withMetadata && baton->withMetadataOrientation != -1) { diff --git a/test/unit/metadata.js b/test/unit/metadata.js index 994cdc04b..f743e0af2 100644 --- a/test/unit/metadata.js +++ b/test/unit/metadata.js @@ -794,15 +794,40 @@ describe('Image metadata', function () { assert.strictEqual(intent, 'Perceptual'); }); - it('withMetadata does not add default sRGB profile to RGB16', async () => { - const data = await sharp(fixtures.inputJpg) - .resize(32, 24) + it('withMetadata adds default sRGB profile to RGB16', async () => { + const data = await sharp({ + create: { + width: 8, height: 8, channels: 4, background: 'orange' + } + }) .toColorspace('rgb16') + .png() .withMetadata() .toBuffer(); const metadata = await sharp(data).metadata(); - assert.strictEqual(undefined, metadata.icc); + assert.strictEqual(metadata.depth, 'ushort'); + + const { description } = icc.parse(metadata.icc); + assert.strictEqual(description, 'sRGB'); + }); + + it('withMetadata adds P3 profile to 16-bit PNG', async () => { + const data = await sharp({ + create: { + width: 8, height: 8, channels: 4, background: 'orange' + } + }) + .toColorspace('rgb16') + .png() + .withMetadata({ icc: 'p3' }) + .toBuffer(); + + const metadata = await sharp(data).metadata(); + assert.strictEqual(metadata.depth, 'ushort'); + + const { description } = icc.parse(metadata.icc); + assert.strictEqual(description, 'sP3C'); }); it('File input with corrupt header fails gracefully', function (done) {