Skip to content

cm: add ICC profile loading#12711

Merged
vaxerski merged 4 commits into
hyprwm:mainfrom
vaxerski:icc
Mar 4, 2026
Merged

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

Conversation

@vaxerski

@vaxerski vaxerski commented Dec 23, 2025

Copy link
Copy Markdown
Member

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
Copy Markdown
Member Author

@fufexan can we get nix

@vaxerski

Copy link
Copy Markdown
Member Author

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

@Flat

Flat commented Dec 23, 2025

Copy link
Copy Markdown

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
Copy Markdown
Member Author

oh oops, I think I forgot v2

@vaxerski

Copy link
Copy Markdown
Member Author

my bad should work now

@vaxerski

Copy link
Copy Markdown
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

Flat commented Dec 23, 2025

Copy link
Copy Markdown

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
Copy Markdown
Member Author

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

@Flat

Flat commented Dec 24, 2025

Copy link
Copy Markdown

Sure, here you go!
iccdump.log

@vaxerski

Copy link
Copy Markdown
Member Author

thanks, despite xmas :P

@vaxerski

vaxerski commented Dec 24, 2025

Copy link
Copy Markdown
Member Author

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

Flat commented Dec 24, 2025

Copy link
Copy Markdown

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

@vaxerski

Copy link
Copy Markdown
Member Author

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

@vaxerski

Copy link
Copy Markdown
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
Copy Markdown
Member Author

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

@UjinT34

UjinT34 commented Dec 25, 2025

Copy link
Copy Markdown
Contributor

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
Copy Markdown
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
Copy Markdown
Member Author

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

@vaxerski

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

Comment thread src/render/OpenGL.cpp Outdated
@freevatar

freevatar commented Dec 26, 2025

Copy link
Copy Markdown
Contributor

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

vaxerski commented Mar 4, 2026

Copy link
Copy Markdown
Member Author

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

vaxerski commented Mar 4, 2026

Copy link
Copy Markdown
Member Author

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

vaxerski commented Mar 4, 2026

Copy link
Copy Markdown
Member Author

if colorimerer passes this is gtg

@Flat

Flat commented Mar 4, 2026

Copy link
Copy Markdown

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

@Flat

Flat commented Mar 4, 2026

Copy link
Copy Markdown

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

vaxerski commented Mar 4, 2026

Copy link
Copy Markdown
Member Author

wtf happened in indigo there

@Flat

Flat commented Mar 4, 2026

Copy link
Copy Markdown

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

vaxerski commented Mar 4, 2026

Copy link
Copy Markdown
Member Author

ABGR16161616F into KMS what the fuck

@vaxerski

vaxerski commented Mar 4, 2026

Copy link
Copy Markdown
Member Author

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
Copy Markdown

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

Flat commented Mar 5, 2026

Copy link
Copy Markdown

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
Copy Markdown

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

wilecoyote2015 commented Mar 5, 2026

Copy link
Copy Markdown

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

UjinT34 commented Mar 5, 2026

Copy link
Copy Markdown
Contributor
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
Copy Markdown

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

vaxerski commented Mar 5, 2026

Copy link
Copy Markdown
Member Author

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

@vaxerski

vaxerski commented Mar 5, 2026

Copy link
Copy Markdown
Member Author

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
Copy Markdown

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.

@Rabioli

Rabioli commented May 12, 2026

Copy link
Copy Markdown

Hello, I don't really know where else to put this, there seems to be a github discussion from a year ago regarding icc profiles, but seems inactive, so I am deciding to write here and not opening a new issue since I believe it is related.

As hyprland updated I decided to test the icc profiles, since my monitor has an awful hdr by default, so I loaded some that I found on the web for my monitor, (it is the exact same model) here is the link to the repo, I tested them all and none of them worked, not even loaded, I enabled debug on hyprland to see what was going on and the only output regarding icc was ERR ]: icc for DP-3 ("/home/user/color/LG_HDR800_DisplayCal_PURE_2.2.icm") failed: Invalid file nothing else, I tested the same profile on KDE and it loaded and applied correctly, I also tested the other profiles in the repo, none of them work, so I am thinking it is not the profile, but I really have no idea why they wouldn't be loading.

Also I checked the profiles with displaycal and iccdump, they seem to be correctly formatted.

I will be checking the discussion closely, and test whatever I can, I don't have a colorimetrer so it would just be with the naked eye.

@Zebra2711

Copy link
Copy Markdown
Contributor

Hello, I don't really know where else to put this, there seems to be a github discussion from a year ago regarding icc profiles, but seems inactive, so I am deciding to write here and not opening a new issue since I believe it is related.

As hyprland updated I decided to test the icc profiles, since my monitor has an awful hdr by default, so I loaded some that I found on the web for my monitor, (it is the exact same model) here is the link to the repo, I tested them all and none of them worked, not even loaded, I enabled debug on hyprland to see what was going on and the only output regarding icc was ERR ]: icc for DP-3 ("/home/user/color/LG_HDR800_DisplayCal_PURE_2.2.icm") failed: Invalid file nothing else, I tested the same profile on KDE and it loaded and applied correctly, I also tested the other profiles in the repo, none of them work, so I am thinking it is not the profile, but I really have no idea why they wouldn't be loading.

Also I checked the profiles with displaycal and iccdump, they seem to be correctly formatted.

I will be checking the discussion closely, and test whatever I can, I don't have a colorimetrer so it would just be with the naked eye.

icc not icm

@vaxerski

Copy link
Copy Markdown
Member Author

both icc and icm usually are the same. I don't know why the loading fails, it's done by lcms

crthpl pushed a commit to crthpl/Hyprland that referenced this pull request Jun 3, 2026
Adds an ICC profile pipeline, loading via config and applying via 3D LUTs.
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.