lovell / lovell/sharp

`pipelineColourspace` with a device-independent colourspace has incorrect color management

Open
#4,595 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

triage
Dominant language
JavaScript
Stars
32.7k
Forks
1.4k
Avg merge
1d 14h
Merged PRs (30d)
5

Description

## Possible bug

### Is this a possible bug in a feature of sharp, unrelated to installation?

- [x] Running `npm install sharp` completes without error.
- [x] Running `node -e "import 'sharp'"` completes without error.

If you cannot confirm both of these, please open an [installation issue](https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md) instead.

### Are you using the latest version of sharp?

- [x] I am using the latest version of `sharp` as reported by `npm view sharp dist-tags.latest`.

If you cannot confirm this, please upgrade to the latest version and try again before opening an issue.

If you are using another package which depends on a version of `sharp` that is not the latest, please open an issue against that package instead.

### What is the output of running `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp`?
```
System:
OS: macOS 26.5
CPU: (14) arm64 Apple M4 Max
Memory: 672.61 MB / 36.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 24.15.0 - /Users/mertalev/.local/share/mise/installs/node/24.15.0/bin/node
npm: 11.12.1 - /Users/mertalev/.local/share/mise/installs/node/24.15.0/bin/npm
pnpm: 11.22.0 - /Users/mertalev/.local/share/mise/installs/pnpm/11.22.0/pnpm
npmPackages:
sharp: ^0.35.4 => 0.35.4
```

### Does this problem relate to file caching?

The default behaviour of libvips is to cache input files, which can lead to `EBUSY` or `EPERM` errors on Windows.
Use [`sharp.cache(false)`](https://sharp.pixelplumbing.com/api-utility#cache) to switch this feature off.

- [x] Adding `sharp.cache(false)` does not fix this problem.

### Does this problem relate to images appearing to have been rotated by 90 degrees?

Images that contain EXIF Orientation metadata are not auto-oriented. By default, EXIF metadata is removed.

- To auto-orient pixel values use the parameter-less [`rotate()`](https://sharp.pixelplumbing.com/api-operation#rotate) operation.
- To retain EXIF Orientation use [`keepExif()`](https://sharp.pixelplumbing.com/api-output#keepexif).

- [x] Using `rotate()` or `keepExif()` does not fix this problem.

### What are the steps to reproduce?

Using an image with an ICC profile as input, set a device-independent pipeline colourspace like `pipelineColourspace('xyz')`. The conversion produces incorrect colours / garbage.

Part of this is because sharp runs `EnsureColourspace` before `icc_transform`, so the input is treated as sRGB. The other part is that when `icc_transform` *does* happen, lcms is handed device-independent float input when it expects device-dependent integer input.

### What is the expected behaviour?

It should transform to the pipeline colourspace using the embedded ICC profile and produce correct colours as output.

### Please provide a minimal, standalone code sample, without other dependencies, that demonstrates this problem

```js
import sharp from 'sharp';

const raw = { width: 8, height: 8, channels: 3 };
const data = Buffer.alloc(8 * 8 * 3);
for (let i = 0; i < data.length; i += 3) data.set([200, 60, 30], i);

// one colour, encoded two ways
const srgb = await sharp(data, { raw }).png().toBuffer();
const p3 = await sharp(data, { raw }).png().withIccProfile('p3').toBuffer();

const render = async (src) => [...await sharp(src).pipelineColourspace('xyz').raw().toBuffer()].slice(0, 3);
console.log('from P3: ', await render(p3));
console.log('from sRGB:', await render(srgb));
```
Output:
```
from P3: [ 23, 14, 2 ]
from sRGB: [ 200, 60, 30 ]
```

This affects scrgb, xyz, yxy, lab, labs and lch; srgb and rgb16 are fine.

### Please provide sample image(s) that help explain this problem

The code above works without a sample, but `test/fixtures/p3.png` reproduces the same issue: `pipelineColourspace('srgb')` gives `255,0,0`, `'xyz'` gives `39,17,0`.

### Additional info

#1317 is relevant as it described using the XYZ connection space to handle embedded input profiles. It seems that part never landed for `pipelineColourspace`.

The motivation of the change is to do resampling in linear-light to address [this](https://github.com/immich-app/immich/issues/30879) issue, which proved to be non-trivial in sharp.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the minimal Node.js reproduction and test/fixtures/p3.png, then trace pipelineColourspace('xyz') through the embedded-profile conversion described in the issue. Compare the EnsureColourspace and icc_transform behavior for device-independent spaces, using #1317 for context. Done means the listed colourspaces produce correct transformed values while srgb and rgb16 remain unaffected.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
computer-graphics
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.