Skip to content

print on mac os: fix double color management when using printer ICC profiles#20721

Merged
TurboGit merged 1 commit intodarktable-org:masterfrom
brettnak:fix/print-icc-color-management
Apr 4, 2026
Merged

print on mac os: fix double color management when using printer ICC profiles#20721
TurboGit merged 1 commit intodarktable-org:masterfrom
brettnak:fix/print-icc-color-management

Conversation

@brettnak
Copy link
Copy Markdown
Contributor

When a printer ICC profile is selected, darktable applies the color conversion via LittleCMS but then fails to communicate this to the downstream CUPS pipeline, causing double color management:

  1. The printer.profile field on the print info struct was never populated from the user's profile selection, so the cm-calibration CUPS option was always set to "false". This meant the CUPS pdftoraster filter (Linux) would re-apply color management to already-converted data. Fixed by propagating the profile selection into printer.profile before calling dt_print_file(). For users using TurboPrint for ICC profiles, this remains 'false' if no ICC profile is selected. @TurboGit - if you're still active, please chime in if this doesn't match your understanding. However, I don't use Darktable on Linux at the moment, so I don't have great ways of testing this. I am not totally sure of this fix, but I believe it is directionally correct towards telling CUPS that the embedded pixel data has already been converted.

  2. On macOS, Apple's cgpdftoraster filter ignores cm-calibration entirely. It reads the PDF color space metadata and assumes DeviceRGB means sRGB, then converts via ColorSync. The printer ICC profile was never embedded in the generated PDF (code existed but was commented out with a "???" note since 2015). Fixed by embedding the profile via dt_pdf_add_icc() so cgpdftoraster recognizes the actual color space.


Test Prints (macos)

  • TOP - Darktable WITH this patch applied
  • MIDDLE - CaptureOne using the same ICC profile. Conversion done via ColorSync within CaptureOne.
  • BOTTOM - Darktable WITHOUT this patch applied.

I have been printing for a while with CaptureOne and I believe the results are good enough to benchmark against, and with this patch, the Darktable prints now match CaptureOne.

dt-print-icc-fixes

@TurboGit
Copy link
Copy Markdown
Member

TurboGit commented Apr 1, 2026

  1. This meant the CUPS pdftoraster filter (Linux) would re-apply color management to already-converted data.

How would it reapply the ICC profile? Is it because the profile is already set on the CUPS printer (global to the OS)?

@brettnak
Copy link
Copy Markdown
Contributor Author

brettnak commented Apr 1, 2026

What it looks like I'm seeing is that on Linux CUPS, pdftoraster (and gstoraster) will apply color management to the raster data if:

  • cm-calibration is not set to true, AND
  • a printer ICC profile is configured (via colord or the PPD's cupsICCProfile entries)

Once the generated PDF is sent to CUPS, I believe this is the code path that would result in an additional icc application:

When cm-calibration=false

  1. cfCmGetCupsColorCalibrateMode() does not find cm-calibrationcm_disabled = 0 (lines 432-438)
  2. !cm_disabled is true → cfCmGetPrinterIccProfile() runs → if a profile exists in colord or the PPD, it's loaded into colorProfile (lines 440-451)
  3. !cm_disabled is true → set_color_profile() runs (lines 2630-2632). This function switches on the printer's requested cupsColorSpace (lines 2316-2397):
    • For ICC color spaces (ICC1-ICCF): colorProfile is preserved if already loaded from step 2 — this is the path that leads to double conversion
  4. At this point popplerColorProfile is still NULL because:
    • It was initialized to NULL in init_cms_profile_t() (line 98)
    • Step 2 only loaded the printer profile into colorProfile, not popplerColorProfile
    • The ICC branch of set_color_profile() in step 3 didn't touch it
  5. Guard check (lines 1381-1384):
    • colorProfile != NULL — true, loaded in step 2
    • popplerColorProfile != colorProfile — true, popplerColorProfile is NULL
    • Both conditions met, enters the transform block
  6. popplerColorProfile is NULL, so it defaults to sRGB (lines 1426-1427)
  7. cmsCreateTransform(sRGB → printer profile) is created and applied to the raster data (lines 1431-1438)
  8. ⚠️ This is a second conversion — darktable already converted the data from sRGB → printer profile via LittleCMS, but pdftoraster doesn't know that and treats it as sRGB again

When cm-calibration=true

  1. cfCmGetCupsColorCalibrateMode() finds cm-calibration in the job options → cm_disabled = 1 (lines 432-436)
  2. !cm_disabled is false → cfCmGetPrinterIccProfile() is skipped → colorProfile stays NULL (line 440)
  3. !cm_disabled is false → set_color_profile() is skipped (lines 2630-2632)
  4. colorProfile != NULL is false → transform block not entered → data passes through unchanged (lines 1381-1384)

The linux change (change 1 in the description) really only affects people who:

  1. Select a printer ICC profile in darktable's print dialog (not "color management in printer driver")
  2. Have a profile configured for that printer in colord or the PPD
  3. Have a printer that has cupsColorSpace as one of the ICC variants (ICC1-ICCF)

That might just be an empty set of people though?


I also did a bit of digging as to what cm-calibration actually means. I doubt this is news to most people who care about printing, but this is the flag to set the CUPS to calibration mode, which skips color management. There's a discussion about it on this mailing list where they change the flag from no-color-management to cm-calibration, I think due to an issue with a flag having no at the start of it.

@brettnak brettnak force-pushed the fix/print-icc-color-management branch from 0687f49 to 5e1f2b3 Compare April 1, 2026 18:24
@TurboGit
Copy link
Copy Markdown
Member

TurboGit commented Apr 1, 2026

The linux change (change 1 in the description) really only affects people who:

Those people are affected, but anyway the setting is wrong. If colord has a profile configured then it is wrong to set the same profile in the print dialog. So I agree with you on this part, this is a wrong user's setting.

I need to understand the other cases... It has been a long time since I have worked on this part. More later.

Comment thread src/libs/print_settings.c
Comment thread src/libs/print_settings.c
@TurboGit TurboGit added bugfix pull request fixing a bug wip pull request in making, tests and feedback needed priority: high core features are broken and not usable at all, software crashes scope: color management ensuring consistency of colour adaptation through display/output profiles release notes: pending scope: print all cups and printing related issues labels Apr 2, 2026
@TurboGit TurboGit added this to the 5.6 milestone Apr 2, 2026
@TurboGit TurboGit marked this pull request as draft April 2, 2026 15:22
When a printer ICC profile is selected, darktable applies the color
conversion via LittleCMS but fails to communicate this to the
downstream CUPS pipeline, causing incorrect color output.

This commit fixes the issue on both Linux and macOS:

1. The printer.profile field on the print info struct was never
   populated from the user's profile selection, so the cm-calibration
   CUPS option was always set to "false". Fixed by propagating the
   profile selection into printer.profile before calling dt_print_file().

2. On Linux, cm-calibration=true tells pdftoraster to skip its own
   color management, preventing double conversion.

3. On macOS, cgpdftoraster ignores cm-calibration entirely. Two
   additional fixes are needed:
   - Embed the ICC profile in the PDF so cgpdftoraster knows the
     pixel data is not sRGB (without this, it misinterprets the
     already-converted data).
   - Send AP_ColorMatchingMode=AP_ApplicationColorMatching (both
     dot and underscore variants) to tell cgpdftoraster and the
     printer driver that the application already handled color
     matching.

Note: on Linux, the ICC profile must NOT be embedded in the PDF —
Poppler (inside pdftoraster) would see the ICCBased color space and
convert the data back to sRGB, undoing the LittleCMS conversion.

Tested on macOS with Canon Pixma Pro-100 against Capture One output.
@brettnak brettnak force-pushed the fix/print-icc-color-management branch from 5e1f2b3 to 51983a4 Compare April 3, 2026 18:54
@brettnak
Copy link
Copy Markdown
Contributor Author

brettnak commented Apr 3, 2026

Okay, I have reworked this PR and printed about 30 test prints to see if i can figure out what's happening, as well as done some decompilation of apple's cgpdftoraster function and canon's Raster2CanonIJS function.

What I've found is that cgpdftoraster will skip all color management if AP_ColorMatchingMode=AP_ApplicationColorMatching... mostly. It seems even though that function actually skips color management, when it calls out to core graphics to actually rasterize, it makes use of the embedded ICC.

Doing some experimentation w/ LittleCMS, I determined that converting pixel data, but not embedding the icc profile leads to preview images that look identical to my prints that are too magenta. So somewhere along the line, probably inside core graphics, something is interpreting DeviceRGB pixels as either sRGB, or some internal mac color space, I'm not really sure which, and then when that canvas is rasterized, the pixels are mismatched from their color space.

So I believe that embedding the ICC profile is the correct thing to do on mac, and I've gated that behind the __APPLE__ macro.

Based on auditing the code for oss/linux cups, I'm not really sure if it's correct to embed the ICC profile, but I suspect not, based on pdftoraster wanting to convert back to srgb. Presumably, to let cups/ppd deal with colors itself.


Incidentally, when I started wondering if lcms and color-sync were just producing different outputs, I discovered that converting an image via lcms and not embedding the icc profile ends up with an image that when previewed, looks exactly like my magenta-shifted prints. Just embedding the icc into one of those already converted images ends up fixing the preview to look correct and match the capture one print and the printed output of this pr.

@brettnak brettnak marked this pull request as ready for review April 3, 2026 19:46
Copy link
Copy Markdown
Member

@TurboGit TurboGit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, please just need a release notes entry. TIA.

@TurboGit TurboGit removed the wip pull request in making, tests and feedback needed label Apr 4, 2026
@TurboGit TurboGit merged commit e75b3d1 into darktable-org:master Apr 4, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix pull request fixing a bug priority: high core features are broken and not usable at all, software crashes release notes: pending scope: color management ensuring consistency of colour adaptation through display/output profiles scope: print all cups and printing related issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants