Skip to content

Join alpha channel with premultiplied tiff file while keeping ICC profile #4565

Description

@adriaanmeuris

Question about an existing feature

Combining a premultiplied CMYK TIFF file with an alpha channel while preserving its ICC profile.

What are you trying to achieve?

First of all, thank you for the great work on this library.

I'm trying to join an alpha channel to an existing CMYK tiff image while retaining the exact CMYK values and the original ICC profile. These are the files I received:

I'm trying to add the alpha channel from the PNG to the TIFF file, keeping the CMYK values exactly as-is.

I attempted to use joinChannel and keep the profile, but I ran into a few different hurdles:

const alphaData = await sharp('rgba.png').extractChannel('alpha').png().toBuffer();
await sharp('cmyk.tiff')
  .joinChannel(alphaData)
  .keepIccProfile()
  .tiff({ compression: 'lzw' })
  .toFile('output.tiff');
console.log((await sharp('output.tiff').metadata()).space); // logs `srgb` instead of `cmyk`
const alphaData = await sharp('rgba.png').extractChannel('alpha').png().toBuffer();
await sharp('cmyk.tiff')
  .joinChannel(alphaData)
  .keepIccProfile()
  .toColourspace('cmyk')       // Added
  .tiff({ compression: 'lzw' })
  .toFile('output.tiff');
console.log((await sharp('output.tiff').metadata()).hasProfile); // logs `false` instead of `true`
const alphaData = await sharp('rgba.png').extractChannel('alpha').png().toBuffer();
await sharp('cmyk.tiff')
  .joinChannel(alphaData)
  .keepIccProfile()
  .toColourspace('cmyk')       // Added
  .pipelineColourspace('cmyk') // Added
  .tiff({ compression: 'lzw' })
  .toFile('output.tiff');
console.log((await sharp('output.tiff').metadata())); // throws `Error: Input image exceeds channel limit`
// running `vipsheader` on the generated file returns `2550x3300 uchar, 8 bands, cmyk, tiffload`

The only way I've succeeded is to re-encode using the source profile explicitly:

const alphaData = await sharp('rgba.png').extractChannel('alpha').png().toBuffer();
await sharp('cmyk.tiff')
  .joinChannel(alphaData)
  .withIccProfile('source-profile.icc')
  .tiff({ compression: 'lzw' })
  .toFile('output.tiff');
console.log((await sharp('output.tiff').metadata())); // CMYK space with 5 bands and ICC attached

This has some drawbacks though:

  1. it requires extracting the ICC profile from the source file first
  2. it re-encodes the file with the same profile, which feels unnecessary as CMYK values can be used as-is
  3. the resulting file still needs to be unpremultiplied as the source TIFF file is premultiplied

For drawback (3), I found a workaround using libvips CLI directly:

vips unpremultiply output.tiff output-unpremultiplied.v
vips cast output-unpremultiplied.v output-unpremultiplied-casted.v uchar
vips tiffsave output-unpremultiplied-casted.v output-unpremultiplied.tiff --compression=lzw

The result output-unpremultiplied.tiff is exactly what I need. Unfortunately, since pipes can't stream these operations in the CLI (libvips/libvips#4256 (comment)), this approach generates intermediate files exceeding 160MB. Chaining operations using a library seems the recommended approach here.

My questions:

  1. Is there a clean way to joinChannel an alpha channel while preserving the ICC profile without having to re-encode using withIccProfile?
  2. What's the feasibility of providing a unpremultiply operation in sharp? (I've written a PR here previously, so I'd be happy to look into this if it's a welcome addition, but I suspect this might be challenging). Alternatively, would resolving sharp() constructor for raw pixel input has no colorspace parameter — blocks CMYK raw roundtrip #4521 be the better approach to handle this entirely with sharp? If I understand correctly, it would let me flag the raw image data as already premultiplied, though I'm unsure if that would prevent me from needing to unpremultiply after merging the alpha channel.

Thank you for your time and guidance.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions