Skip to content

cm: add ICC profile loading#12711

Merged
vaxerski merged 4 commits intohyprwm:mainfrom
vaxerski:icc
Mar 4, 2026
Merged

cm: add ICC profile loading#12711
vaxerski merged 4 commits intohyprwm:mainfrom
vaxerski:icc

Conversation

@vaxerski
Copy link
Member

@vaxerski vaxerski commented Dec 23, 2025

Adds loading of icc profiles for the properties we support

ref #9064

Usage

v2: icc = /full/path
v1: , icc, /full/path

Info

This also changes how we handle CM in general, where our intermediate buffer (offload) is in sRGB, and we CM at the end.

This should also make it possible to have actual, proper pixel-perfect screencopy in a follow-up MR (as we can copy the offloaded fb for screencopy purposes) - But that's for a follow-up MR

Requires:

  • HDR offs ICC
  • Testing!!!
  • Fix fucking render glitches XDDDD
  • Clean up the code its a mess of different ideas
  • (optional?) Load VCGT ramps into KMS
  • wiki for icc and new variables
  • Screencopy must not sample CM'd buffer

@vaxerski
Copy link
Member Author

@fufexan can we get nix

@vaxerski
Copy link
Member Author

can you guys let me know if this works well? I have done very limited testing.

@Flat
Copy link

Flat commented Dec 23, 2025

can you guys let me know if this works well? I have done very limited testing.

Hyprland 0.52.0 built from branch icc-support at commit a3520d917afdbbd084c76a8daa8ce1d6c02811f5 clean (cm: add ICC profile loading).
Date: Tue Dec 23 11:06:26 2025
Tag: v0.52.0-183-ga3520d91, commits: 6742

Libraries:
Hyprgraphics: built against 0.4.0, system has 0.4.0
Hyprutils: built against 0.11.0, system has 0.11.0
Hyprcursor: built against 0.1.13, system has 0.1.13

Hyprlang: built against 0.6.7, system has 0.6.7
Aquamarine: built against 0.10.0, system has 0.10.0

Do not see anything in the logs showing it is loading the ICC profile, or rather nothing icc at all. What am I doing wrong there?

monitorv2 {
    output = DP-2
    mode = 3440x1440@143.97
    scale = 1
    position = 0x0
    bitdepth = 10
    cm = dcip3
    sdr_min_luminance = 0.0005
    sdr_max_luminance = 250
    icc = /home/flat/.config/icc/dell-icc-profile.icm
}
monitorv2 {
    output = DP-1
    mode = 3440x1440@143.97
    scale = 1
    position = auto-up
    bitdepth = 10
    cm = dcip3
    sdr_min_luminance = 0.005
    sdr_max_luminance = 250
    icc = /home/flat/.config/icc/dell-icc-profile.icm
}

hyprland.log

@vaxerski
Copy link
Member Author

oh oops, I think I forgot v2

@vaxerski
Copy link
Member Author

my bad should work now

@vaxerski
Copy link
Member Author

it should dump what we were able to decode from the icc into the log like so if it loaded successfully

image

@Flat
Copy link

Flat commented Dec 23, 2025

Yup, it does attempt to load now. Unfortunately looks like the only ICC profile for my monitor is split-trc, so I won't be able to assist much with testing here.

failed: Hyprland cannot represent split-trc profiles yet

@vaxerski
Copy link
Member Author

I've added logging of the trc to the debug output, can you post the icc data dump here?

@Flat
Copy link

Flat commented Dec 24, 2025

Sure, here you go!
iccdump.log

@vaxerski
Copy link
Member Author

thanks, despite xmas :P

@vaxerski
Copy link
Member Author

vaxerski commented Dec 24, 2025

This should work. I've loaded my ICC profile and it does seem to change the colors on my panel a bit, but without a colorimeter I have no way of verifying if it's correct. Please load your ICC and let me know.

@Flat
Copy link

Flat commented Dec 24, 2025

It does seem to change the colors. I've ordered a colorimeter to verify though, should have it within a week.

@vaxerski
Copy link
Member Author

talk about dedication. I do think its wrong, sway's is different.

@vaxerski
Copy link
Member Author

@UjinT34 would you mind checking the LUT shader changes? I'm quite sure I am doing something wrong there. Shadows are a tad too bright I think.

Can't say for sure without a colorimeter but they do look a bit flat and weird.

@vaxerski
Copy link
Member Author

I checked KDE, colors shift a tad but not as much as we do. Something's wrong.

@UjinT34
Copy link
Contributor

UjinT34 commented Dec 25, 2025

I am not that familiar with math around icc luts. clamp looks sus to me, might be too early. EXT_LINEAR is expected to go outside of 0.0 - 1.0 and probably should be clamped after all the transformations.

@vaxerski
Copy link
Member Author

fixed, but it's again surfacing that nightmare where kitty sets the DEFAULTS and yet it changes how we render shit. I need to do something about this...

@vaxerski
Copy link
Member Author

nvm its not fixed it just had a bug where it wouldnt apply cm curves fuck my life

@vaxerski
Copy link
Member Author

in general @UjinT34 shouldn't we compose to a linear SRGB buffer internally and then do a final CM pass on the entire image? Why are we doing it per-surface?

This would be desirable as ICC shifts colors and will mess up e.g. color pickers. We should screencopy un-icc'd buffers.

@github-actions github-actions bot added the nix label Dec 25, 2025
@vaxerski
Copy link
Member Author

I've pushed a completely different approach with a 3D LUT instead, which also changes how we render CM. This will need some testing.

This needs an HDR-kill-switch internally (HDR should disable ICC, currently it's likely the other way round)

This needs people to run this shit and report any visual glitches or bugs, feel free to drive this

@Flat thanks for your testing, if you could compare how this looks vs KDE it would be great. Sway's ICC is busted on my end, but KWin's works properly.

@vaxerski
Copy link
Member Author

I've decided to use Gamma 2.2 for internal storage just like kwin does, this lifts up the shadows a bit and looks more accurate in my opinion.

@freevatar
Copy link

freevatar commented Dec 26, 2025

Yay! This is my use case! I have a colorimeter, good wide gamut display, its ICC profile, and the display itself has a pretty accurate sRGB clamp which I measured.

I hope to have some time to test this PR this weekend.

Meanwhile the only program I've found that applies ICC profile correctly (for sRGB clamp) is https://github.com/ledoge/novideo_srgb. But its for Windows. Every Linux DE implementation I tried was way off color-wise, at least the last time I checked.

@vaxerski
Copy link
Member Author

vaxerski commented Mar 2, 2026

also out of curiosity @wilecoyote2015 I didnt ask but I assume this is better than without icc? xD

Idk if you've tested gnome/kwin to compare.

@fufexan
Copy link
Member

fufexan commented Mar 2, 2026

@fufexan wtf is this nix conflict

I changed the formatting to the standard on main. Just rebase and redo the nix file or whatever, it's two lines.

@wilecoyote2015
Copy link

wilecoyote2015 commented Mar 3, 2026

also out of curiosity @wilecoyote2015 I didnt ask but I assume this is better than without icc? xD

Yes, much better than without! Without ICC, the color is way off. In the case of my display, it covers the DCIP3 gamut, but does not match the DCIP3 response well at all. So even when using the dcip3 cm preset, colors are off and delta E is in the invalid region everywhere.

@wilecoyote2015 honestly dunno, @UjinT34 advocated for gamma2.2 as apparently kwin et al use that (and yea without icc it looks more correct) but perhaps ICC should fall back to srgb.

Honestly, dunno, too. Although that color management stuff is based on straight forward math in principle, I still struggle to wrap my head about the specific details how the pieces come together as correct color management has so many entangled layers of physical and perceptual aspects xD

Here is a short chat I had with claude Opus 4.6 about this topic - cannot verify the facts in depth. But the bottom line at least corresponds to my intuition to some degree:

  • As the eotf setting in Hyprland is only an assumption for clients not communicating their color space, it does not harm the rendering of graphic applications that do proper color management. Hence the discrepancy in the eotf between gamma 2.2. and sRGB is not a deal breaker for color managed work (under the assumption that color managed graphic applications will use the protocol to communicate their color space properly in the future).
  • So the current behavior is okay for most users I guess, and forcing sRGB when ICC is on should help to get correct color handling for non-color-managed applications by default. Maybe only problem here is that forcing sRGB for iCC makes configuration less understandable for the user?
  • it is maybe a conceptional question whether the non-sRGB eotf should really be used to decode client colors, or whether color decoding should always happen in a correct way with sRGB, so that Hyprland always has correct internal color representation from the clients and there should be an option to darken the shadows when doing the output transform for emulating a gamma 2.2 display or the corresponding rendering of windows / kde or whatever. This would eliminate the sdr eotf setting I guess?
    chat_claude_opus_4_6_eotf.md

@vaxerski
Copy link
Member Author

vaxerski commented Mar 4, 2026

while working on screencopy I realized the work buffer was actually not srgb but I can't seem to get pc to work again

@vaxerski
Copy link
Member Author

vaxerski commented Mar 4, 2026

fixed, screencopy should work now too.

@wilecoyote2015 sorry for being so annoying but could you recheck the colorimeter, plus check if CM doesn't disturb screencopy? (screenshots / screen recordings) - these should be in SRGB, IOW if I open a terminal on srgb no icc and use a color picker e.g. hyprpicker to pick the background, the same #hex should be printed with icc.

I ran some tests and on my end looks good

@vaxerski
Copy link
Member Author

vaxerski commented Mar 4, 2026

if colorimerer passes this is gtg

@Flat
Copy link

Flat commented Mar 4, 2026

Working on updating ArgyllCMS and Displaycal to give another test as well. Hopefully have that test done in next couple hours.

@Flat
Copy link

Flat commented Mar 4, 2026

Still about the same for me, using the same verification options as wileycoyote2015.
Hyprland No ICC.html
Hyprland ICC Applied.html
Plasma ICC Applied.html

No ICC:
image

ICC:
image

KDE:
image

Same profile profiled on Hyprland used on Hyprland and KDE tests.

Profiling Settings:
image

Resultant ICC Profile:
Display-DP-2-Hyprland.icc.tar.gz

@vaxerski
Copy link
Member Author

vaxerski commented Mar 4, 2026

wtf happened in indigo there

@Flat
Copy link

Flat commented Mar 4, 2026

Million dollar question 😀.

Ujin suggested it could be a float precision issue since blues were closest to 0, #12711 (comment)

Or a difference in colorspaces. Here's a diff between drm_info on hyprland and kde

85c85
< │   │       ├───"EDID" (immutable): blob = 147
---
> │   │       ├───"EDID" (immutable): blob = 154
136c136
< │   │       ├───"EDID" (immutable): blob = 152
---
> │   │       ├───"EDID" (immutable): blob = 147
161c161
< │           ├───"EDID" (immutable): blob = 148
---
> │           ├───"EDID" (immutable): blob = 152
217c217
< │   │       ├───"MODE_ID" (atomic): blob = 160
---
> │   │       ├───"MODE_ID" (atomic): blob = 163
237c237
< │   │       ├───"MODE_ID" (atomic): blob = 167
---
> │   │       ├───"MODE_ID" (atomic): blob = 164
291,292c291,292
<     │   │   ├───FB ID: 184
<     │   │   │   ├───Object ID: 184
---
>     │   │   ├───FB ID: 160
>     │   │   │   ├───Object ID: 160
294c294
<     │   │   │   ├───Format: XBGR2101010 (0x30334258)
---
>     │   │   │   ├───Format: ABGR16161616F (0x48344241)
297c297
<     │   │   │       └───Plane 0: offset = 0, pitch = 13760 bytes
---
>     │   │   │       └───Plane 0: offset = 0, pitch = 27520 bytes
324,325c324,325
<     │       ├───"FB_ID" (atomic): object framebuffer = 184
<     │       │   ├───Object ID: 184
---
>     │       ├───"FB_ID" (atomic): object framebuffer = 160
>     │       │   ├───Object ID: 160
327c327
<     │       │   ├───Format: XBGR2101010 (0x30334258)
---
>     │       │   ├───Format: ABGR16161616F (0x48344241)
330c330
<     │       │       └───Plane 0: offset = 0, pitch = 13760 bytes
---
>     │       │       └───Plane 0: offset = 0, pitch = 27520 bytes
537,540c537,540
<     │       ├───"CRTC_X" (atomic): srange [INT32_MIN, INT32_MAX] = 0
<     │       ├───"CRTC_Y" (atomic): srange [INT32_MIN, INT32_MAX] = 0
<     │       ├───"CRTC_W" (atomic): range [0, INT32_MAX] = 0
<     │       ├───"CRTC_H" (atomic): range [0, INT32_MAX] = 0
---
>     │       ├───"CRTC_X" (atomic): srange [INT32_MIN, INT32_MAX] = 467
>     │       ├───"CRTC_Y" (atomic): srange [INT32_MIN, INT32_MAX] = 1423
>     │       ├───"CRTC_W" (atomic): range [0, INT32_MAX] = 256
>     │       ├───"CRTC_H" (atomic): range [0, INT32_MAX] = 256
543,544c543,544
<     │       ├───"SRC_W" (atomic): range [0, UINT32_MAX] = 0
<     │       ├───"SRC_H" (atomic): range [0, UINT32_MAX] = 0
---
>     │       ├───"SRC_W" (atomic): range [0, UINT32_MAX] = 256
>     │       ├───"SRC_H" (atomic): range [0, UINT32_MAX] = 256
778,779c778,779
<     │   │   ├───FB ID: 171
<     │   │   │   ├───Object ID: 171
---
>     │   │   ├───FB ID: 148
>     │   │   │   ├───Object ID: 148
781c781
<     │   │   │   ├───Format: XBGR2101010 (0x30334258)
---
>     │   │   │   ├───Format: ABGR16161616F (0x48344241)
784c784
<     │   │   │       └───Plane 0: offset = 0, pitch = 13760 bytes
---
>     │   │   │       └───Plane 0: offset = 0, pitch = 27520 bytes
811,812c811,812
<     │       ├───"FB_ID" (atomic): object framebuffer = 171
<     │       │   ├───Object ID: 171
---
>     │       ├───"FB_ID" (atomic): object framebuffer = 148
>     │       │   ├───Object ID: 148
814c814
<     │       │   ├───Format: XBGR2101010 (0x30334258)
---
>     │       │   ├───Format: ABGR16161616F (0x48344241)
817c817
<     │       │       └───Plane 0: offset = 0, pitch = 13760 bytes
---
>     │       │       └───Plane 0: offset = 0, pitch = 27520 bytes
1016,1017c1016,1017
<     │   │   ├───FB ID: 174
<     │   │   │   ├───Object ID: 174
---
>     │   │   ├───FB ID: 166
>     │   │   │   ├───Object ID: 166
1027,1028c1027,1028
<     │       ├───"FB_ID" (atomic): object framebuffer = 174
<     │       │   ├───Object ID: 174
---
>     │       ├───"FB_ID" (atomic): object framebuffer = 166
>     │       │   ├───Object ID: 166
1036,1037c1036,1037
<     │       ├───"CRTC_X" (atomic): srange [INT32_MIN, INT32_MAX] = 1720
<     │       ├───"CRTC_Y" (atomic): srange [INT32_MIN, INT32_MAX] = 720
---
>     │       ├───"CRTC_X" (atomic): srange [INT32_MIN, INT32_MAX] = 799
>     │       ├───"CRTC_Y" (atomic): srange [INT32_MIN, INT32_MAX] = 386

@vaxerski
Copy link
Member Author

vaxerski commented Mar 4, 2026

ABGR16161616F into KMS what the fuck

@vaxerski
Copy link
Member Author

vaxerski commented Mar 4, 2026

fuck it I'm doing a hyprland development

image

@vaxerski vaxerski merged commit 1075474 into hyprwm:main Mar 4, 2026
9 of 10 checks passed
@MajedAlghoul
Copy link

update: running main version, and setting
render {
cm_enabled = false
}
fixes the indigo issue for me and displays colors seemingly identical to gnome running the same icc profile

thanks.

@Flat
Copy link

Flat commented Mar 5, 2026

update: running main version, and setting
render {
cm_enabled = false
}
fixes the indigo issue for me and displays colors seemingly identical to gnome running the same icc profile

thanks.

This would disable the ICC profile from being applied. I checked my profile on gnome and it will not apply ICC profiles globally unless they are calibrated profiles. If in gnome it has an information icon that you mouse over it will say this profile cannot be used for whole display color correction or something to that effect.

@MajedAlghoul
Copy link

update: running main version, and setting
render {
cm_enabled = false
}
fixes the indigo issue for me and displays colors seemingly identical to gnome running the same icc profile
thanks.

This would disable the ICC profile from being applied. I checked my profile on gnome and it will not apply ICC profiles globally unless they are calibrated profiles. If in gnome it has an information icon that you mouse over it will say this profile cannot be used for whole display color correction or something to that effect.

well mine applies globally in both cases, hyprland and gnome, i checked gnome and yeah my profile doesnt have an information icon

@wilecoyote2015
Copy link

wilecoyote2015 commented Mar 5, 2026

Please correct me if I'm wrong - my info on gnome and KDE CM is a bit outdated (X11 times ..):
In Gnome and KDE, the profiling information is not used for transforming displayed colors by the compositor.
Only the calibration information of the profile is used for that.
The intended workflow there was the following:

  1. Calibrate your monitor to a desired response (e.g. make it match sRGB roughly)
  2. Apply the calibration in the Compositor
  3. Profile the calibrated display to get an accurate parameterization of how your calibrated display actually responds
  4. The profile is communicated to color managed applications so that they can do the transform and also more sophisticated stuff like different rendering intents, black level correction, soft proofing and so on

The latter is BTW a good reason why one might like to disable CM for some application and let the application handle the display transform entirely: different rendering intents or blackpoint correction than Hyprland does may be desired

@UjinT34
Copy link
Contributor

UjinT34 commented Mar 5, 2026

4. The profile is communicated to color managed applications so that they can do the transform and also more sophisticated stuff like different rendering intents, black level correction, soft proofing and so on

With this PR the ICC profile isn't a part of the CM proto and any app supporting the proto will treat the monitor as a default unprofiled gamma22 sRGB. Some internals might also ignore the ICC part unless specifically modified to handle it.

@wilecoyote2015
Copy link

I've also run the verification - but this time calibrated and verified with 10 bit and used the large verification chart.
As before, brighter colors are fine - darker ones are way off. Using srgb eotf
image
image

Measurement Report 3.9.17 - Web @ localhost - 2026-03-05 17-04.html

@vaxerski
Copy link
Member Author

vaxerski commented Mar 5, 2026

that tbh could suggest a precision problem because dark colors are the most susceptible to this

@vaxerski
Copy link
Member Author

vaxerski commented Mar 5, 2026

we could likely try pushing F16 buffers into KMS later and see if that helps but that would be after ujin's renderer refactors I don't want to cause another rebase mess xD

@MajedAlghoul
Copy link

Thinking about it. Maybe it has to do with screen capability to cover what percentage of srgb. I know my screen doesn't cover a good percentage. From what I understand using an icc forces srgb. Not sure if this makes sense. I just thought of sharing it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.