Join alpha channel with premultiplied tiff file while keeping ICC profile
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 32.7k
- Forks
- 1.4k
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 5
Description
## 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:
- [`cmyk.tiff`](https://github.com/user-attachments/files/29556721/cmyk.tiff): 4 bands + ICC profile
- [`rgba.png`](https://github.com/user-attachments/assets/7616dbc2-6bea-4a20-9cfd-16c00d677fd5): 3 bands + alpha channel.
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:
```js
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`
```
```js
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`
```
```js
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:
```js
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:
```sh
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 (https://github.com/libvips/libvips/discussions/4256#discussioncomment-11244848), 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 https://github.com/lovell/sharp/issues/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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the documented joinChannel, keepIccProfile, toColourspace, and pipelineColourspace cases with the supplied CMYK TIFF and RGBA PNG. Compare the output metadata and the libvips CLI unpremultiply workflow, then review issue #4521 and the existing sharp API for the smallest supported path. Done means the intended operation and scope are confirmed, or the request is narrowed to a concrete change.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, nodejs
- Domain
- computer-graphics
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100