9
175
Fork
You've already forked wallust
21

Gather and sort colors by salience instead of brightness #188

Merged
explosion-mental merged 92 commits from usaradark/wallust:salience-v5 into master 2025年11月30日 00:53:31 +01:00
Contributor
Copy link

This pull request introduces the concept of Salience into the repository. Salience describes how much a color pops out and grabs your attention on a background. The wal ecosystem expects color palettes to be sorted from dimmest to brightest for dark theme (flipped for light), with the intention of assigning brighter colors to more interesting things. However, what actually ends up happening is that dimmer colors can dominate attention if they are saturated or complementary to the background hue. Sorting by salience solves this.

Assuming the user and/or their tools have properly assigned colors, there are major improvements to clarity and critical on-screen elements. This is an example with code highlighting.

I like to assign colors from least to most important as follows:
Punctuation < Context (Namesapces) < Nouns (Objects) < Verbs (Functions) < Comments < Creation (Declaration/Definition)
I haven't polished the color assignment yet for Rust, but it's good enough to demonstrate the proposal. More examples and the initial issue and discussion can be found at #186.

Sorting by brightness (Current)
image

Sorting by salience (Proposed)
image

@explosion-mental you have been given edit perms. I rebased to upstream/master but I honestly don't know if I did it correctly. I was also unable to build due to some changes in src/cache.rs (see bb6012f0e5). I also noticed that when it showed the diff, it also show all of your changes on top of mine, which was weird. I haven't done anything this big before so forgive me for any incompetence for merging. Just let me know what I need to do.

Also, when creating src/palette/saliencelight.rs, interestingly, I just copy and pasted the dark version and changed some values and functions. There's actually a LOT of shared logic, and it would benefit from merging the logic. I can work on this later, but for now you can look at everything else. If you do choose to take a look at the palette salience implementation, focus on saliencedark, as that's where I based most of my logic from, and when I refactor, will be focusing on that side.

I'm also assuming we probably want to squash everything somehow later.

This pull also introduces the potential to alter how to sample colors from the image. With a max of 16 colors that can be passed to the palette generator, we can sample colors from the lowest, highest, centered, or distributed. This results in varying contrast and intensity to the finalized palette. For dark, these palettes are dubbed lowsaliencedark, saliencedark, and highsalienedark, with saleincedark being either sampled centered or distributed depending if dark or light theme.

This pull request introduces the concept of Salience into the repository. Salience describes how much a color pops out and grabs your attention on a background. The wal ecosystem expects color palettes to be sorted from dimmest to brightest for dark theme (flipped for light), with the intention of assigning brighter colors to more interesting things. However, what actually ends up happening is that dimmer colors can dominate attention if they are saturated or complementary to the background hue. Sorting by salience solves this. Assuming the user and/or their tools have properly assigned colors, there are major improvements to clarity and critical on-screen elements. This is an example with code highlighting. I like to assign colors from least to most important as follows: `Punctuation` < `Context (Namesapces)` < `Nouns (Objects)` < `Verbs (Functions)` < `Comments` < `Creation (Declaration/Definition)` I haven't polished the color assignment yet for Rust, but it's good enough to demonstrate the proposal. More examples and the initial issue and discussion can be found at #186. Sorting by brightness (Current) ![image](/attachments/c5d6e0c6-3115-4d50-aabb-b81edf5eeef9) Sorting by salience (Proposed) ![image](/attachments/ff09b074-adbf-4753-9ea2-4f37c5d93e5f) @explosion-mental you have been given edit perms. I rebased to upstream/master but I honestly don't know if I did it correctly. I was also unable to build due to some changes in `src/cache.rs` (see [bb6012f0e5](https://codeberg.org/usaradark/wallust/commit/bb6012f0e5a9f3d514f61b0ce7ca0f55c45346cd)). I also noticed that when it showed the diff, it also show all of your changes on top of mine, which was weird. I haven't done anything this big before so forgive me for any incompetence for merging. Just let me know what I need to do. Also, when creating `src/palette/saliencelight.rs`, interestingly, I just copy and pasted the dark version and changed some values and functions. There's actually a LOT of shared logic, and it would benefit from merging the logic. I can work on this later, but for now you can look at everything else. If you do choose to take a look at the palette salience implementation, focus on `saliencedark`, as that's where I based most of my logic from, and when I refactor, will be focusing on that side. I'm also assuming we probably want to squash everything somehow later. This pull also introduces the potential to alter how to sample colors from the image. With a max of 16 colors that can be passed to the palette generator, we can sample colors from the lowest, highest, centered, or distributed. This results in varying contrast and intensity to the finalized palette. For dark, these palettes are dubbed `lowsaliencedark`, `saliencedark`, and `highsalienedark`, with `saleincedark` being either sampled centered or distributed depending if dark or light theme.
3.8 MiB
3.8 MiB
usaradark changed title from (削除) salience-v5 (削除ここまで) to WIP: salience-v5 2025年11月05日 05:55:12 +01:00
usaradark changed title from (削除) WIP: salience-v5 (削除ここまで) to WIP: Gather and sort colors by salience instead of brightness 2025年11月05日 05:55:50 +01:00

I was also unable to build due to some changes in src/cache.rs (see bb6012f0e5).

Just rebase from master, since the changes I made haven't touched any of your files, thus, there are no conflicts.

I'm also assuming we probably want to squash everything somehow later.

Yes, I can do that when the PR is ready, don't worry about it, I prefer to squash rather than merge to keep a clean history, since all the history stays in the PR, which is useful given that it needs some cleaning.

I'll be free on a few hours to help, I just got a notification, keep up the good work!

Edit: If it helps, explaining it as "simple", yet with a potential of technicality, as possible ínside the documentation helps to clean my head sometimes, gives some lead to what does it do

> I was also unable to build due to some changes in src/cache.rs (see bb6012f0e5). Just rebase from master, since the changes I made haven't touched any of your files, thus, there are no conflicts. > I'm also assuming we probably want to squash everything somehow later. Yes, I can do that when the PR is ready, don't worry about it, I prefer to squash rather than merge to keep a clean history, since all the history stays in the PR, which is useful given that it needs some cleaning. I'll be free on a few hours to help, I just got a notification, keep up the good work! Edit: If it helps, explaining it as "simple", yet with a potential of technicality, as possible ínside the documentation helps to clean my head sometimes, gives some lead to what does it do
usaradark force-pushed salience-v5 from 6805202fff
Some checks are pending
ci/woodpecker/pr/check Pipeline is pending approval
ci/woodpecker/pr/docs Pipeline is pending approval
ci/woodpecker/pr/nix-build Pipeline is pending approval
to 75c90e5f3b
Some checks are pending
ci/woodpecker/pr/check Pipeline is pending approval
ci/woodpecker/pr/nix-build Pipeline is pending approval
2025年11月05日 06:11:20 +01:00
Compare
Author
Contributor
Copy link

Just rebase from master, since the changes I made haven't touched any of your files, thus, there are no conflicts.

Somehow did the rebase incorrectly since it applied your recent changes after mine (mine should be after yours). Fixed with the force push and now looks a lot better.

But what I meant by unable to build is that I was getting this error:

error[E0277]: `PathBuf` doesn't implement `std::fmt::Display`
 --> src/cache.rs:76:77
 |
76 | ... format!("Failed to create {cachepath}"))?;
 | ^^^^^^^^^^^ `PathBuf` cannot be formatted with the default formatter; call `.display()` on it
 |
 = help: the trait `std::fmt::Display` is not implemented for `PathBuf`
 = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
 = note: call `.display()` or `.to_string_lossy()` to safely print paths, as they may contain non-Unicode data
 = note: this error originates in the macro `$crate::__export::format_args` which comes from the expansion of the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info)

I'm currently on cargo 1.93.0-nightly on Archlinux. Stable 1.86.0 wouldn't build.

edit - I will be sleeping soon so will be gone for a while.

> Just rebase from master, since the changes I made haven't touched any of your files, thus, there are no conflicts. Somehow did the rebase incorrectly since it applied your recent changes after mine (mine should be after yours). Fixed with the force push and now looks a lot better. But what I meant by unable to build is that I was getting this error: ``` error[E0277]: `PathBuf` doesn't implement `std::fmt::Display` --> src/cache.rs:76:77 | 76 | ... format!("Failed to create {cachepath}"))?; | ^^^^^^^^^^^ `PathBuf` cannot be formatted with the default formatter; call `.display()` on it | = help: the trait `std::fmt::Display` is not implemented for `PathBuf` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead = note: call `.display()` or `.to_string_lossy()` to safely print paths, as they may contain non-Unicode data = note: this error originates in the macro `$crate::__export::format_args` which comes from the expansion of the macro `format` (in Nightly builds, run with -Z macro-backtrace for more info) ``` I'm currently on cargo 1.93.0-nightly on Archlinux. Stable 1.86.0 wouldn't build. edit - I will be sleeping soon so will be gone for a while.
Author
Contributor
Copy link

I finally got clippy integrated with my LSP so I'm running through some of it's suggested changes.

edit - done

I finally got clippy integrated with my LSP so I'm running through some of it's suggested changes. edit - done
explosion-mental force-pushed salience-v5 from 0aa9c09956
Some checks are pending
ci/woodpecker/pr/check Pipeline is pending approval
ci/woodpecker/pr/docs Pipeline is pending approval
ci/woodpecker/pr/nix-build Pipeline is pending approval
to f7147ec856 2025年11月05日 20:38:41 +01:00
Compare

Hey I got a question, what does process_dedup do? along with clip_cols? I think it does nothing right now, maybe you had an idea an leave it?:p

BTW I'm cleaning the print statements, maybe later I can implement a logging system, since I do use prints a lot as well.

Hey I got a question, what does `process_dedup` do? along with `clip_cols`? I think it does nothing right now, maybe you had an idea an leave it?:p BTW I'm cleaning the print statements, maybe later I can implement a logging system, since I do use prints a lot as well.
Author
Contributor
Copy link

Hey I got a question, what does process_dedup do? along with clip_cols? I think it does nothing right now, maybe you had an idea an leave it?:p

So on master, dedup_cols does deduplication + sort + truncate. I split those three steps into three separate functions. I think I did this because I noticed that I needed to only modify the sorting step, which was a post-processing after deduplication in preparation to truncating (clip_cols). Additionally, I originally thought I needed additional context for post-processing (ord and bytes)... but it turns out in the end I never needed them....

Actually... there's a bug here. I actually needed to consider ord to consider dark AND light theme (only dark theme considered). Currently colors are only sorted for dark theme, then truncated. Let me fix that.

> Hey I got a question, what does process_dedup do? along with clip_cols? I think it does nothing right now, maybe you had an idea an leave it?:p So on master, `dedup_cols` does deduplication + sort + truncate. I split those three steps into three separate functions. I think I did this because I noticed that I needed to only modify the sorting step, which was a post-processing after deduplication in preparation to truncating (clip_cols). Additionally, I originally thought I needed additional context for post-processing (`ord` and `bytes`)... but it turns out in the end I never needed them.... Actually... there's a bug here. I actually needed to consider `ord` to consider dark AND light theme (only dark theme considered). Currently colors are only sorted for dark theme, then truncated. Let me fix that.
explosion-mental changed title from (削除) WIP: Gather and sort colors by salience instead of brightness (削除ここまで) to Gather and sort colors by salience instead of brightness 2025年11月06日 02:20:00 +01:00
explosion-mental changed title from (削除) Gather and sort colors by salience instead of brightness (削除ここまで) to WIP: Gather and sort colors by salience instead of brightness 2025年11月06日 02:20:28 +01:00

Btw, just as a thought experiment, maybe for future impl. Shouldn't SamplingMode and ColorOrder be merged? Maybe in theory, but in practice it means to implement salience for lab,lch and so on. I'm still working out the general structure of the whole backend -> colorspace -> palette way of doing things, currently tho, I think it's good enough.

btw 2 I'd noticed the need of comunication between colorspace module and the palette one, yet somehow you made it good enough with the SamplingMode.

Btw, just as a thought experiment, maybe for future impl. Shouldn't `SamplingMode` and `ColorOrder` be merged? Maybe in theory, but in practice it means to implement salience for lab,lch and so on. I'm still working out the general structure of the whole `backend` -> `colorspace` -> `palette` way of doing things, currently tho, I think it's good enough. btw 2 I'd noticed the need of comunication between colorspace module and the palette one, yet somehow you made it good enough with the SamplingMode.
Author
Contributor
Copy link

ColorOrder is weird when it comes in sorting by salience. Given a dark background, it's already implied that the color order will be darks first and lights last, assuming sorting by least to most salient. So in a way, ColorOrder is only used to pick the background, then once the background is picked, it isn't needed anymore. I think this was how communication between colorspace and palette worked out okay?

SamplingMode != ColorOrder... but now that I think about it, I can see how you might think the two the similar. It actually might work. I created SamplingMode to solve the problem of rather extreme contrast, where instead of choosing the most salient colors, we choose something less salient or distributed so not EVERY COLOR screams for your attention. But iirc your implementation of darks vs lights does a reverse sort and samples the front of the list, which is similar to SamplingMode::Low. I think it's possible... but that's for later.

Addition - also tweaking some weights and values for math. Shouldn't impact your review too much.
Edit - Done

Addition 2 - I'm actually kind of liking light theme now.

`ColorOrder` is weird when it comes in sorting by salience. Given a dark background, it's already implied that the color order will be darks first and lights last, assuming sorting by least to most salient. So in a way, ColorOrder is only used to pick the background, then once the background is picked, it isn't needed anymore. I think this was how communication between colorspace and palette worked out okay? `SamplingMode` != `ColorOrder`... but now that I think about it, I can see how you might think the two the similar. It actually might work. I created `SamplingMode` to solve the problem of rather extreme contrast, where instead of choosing the most salient colors, we choose something less salient or distributed so not EVERY COLOR screams for your attention. But iirc your implementation of darks vs lights does a reverse sort and samples the front of the list, which is similar to `SamplingMode::Low`. I think it's possible... but that's for later. Addition - also tweaking some weights and values for math. Shouldn't impact your review too much. Edit - Done Addition 2 - I'm actually kind of liking light theme now.

Now that I come to think about it.. that may be it. Instead of choosing LCH-LAB, just choose, light/dark/highsalience or low salience or balanced salience. As for the Palettes variations, it should be dark/light plus some variations, like ANSI and/or some HARD-SOFT (hard colors or "soft" pastel colors like)... MIght be interesting.

Now that I come to think about it.. that may be it. Instead of choosing LCH-LAB, just choose, light/dark/highsalience or low salience or balanced salience. As for the Palettes variations, it should be dark/light plus some variations, like ANSI and/or some HARD-SOFT (hard colors or "soft" pastel colors like)... MIght be interesting.
Author
Contributor
Copy link

Uh... I don't really follow, but I'm assuming you mean like...

gather colors -> decide sorting method (brightness/salience) -> decide sampling method ->
-> decide if post-processing (hard/soft)

On the sorting method, earlier colors will always be on the left side (index 0) and later colors will always be on the right side (index 7). If this is what you mean, I think it would be rather elegant.

Currently sampling and post-processing are coupled together which makes it weird to make variations from the gathered colors.

But you need to be careful on post-processing, because it could alter how colors should be sorted. But that could be fixed with a re-sort or apply the post-processing equally across all colors.

Another thing is that we actually might not need to truncate the colors. I know you have a MAX_COLS=16 assigned set. I think you have this in place for the dynamic thresholding to work? Something I kind of wished is for the dynamic thresholding to generate more colors so that the sampling method has more colors that it could sample from. Like for the SamplingMode::Distributed, I like how it spreads out the attention across colors. But when there's not enough colors, it has little to no difference whether we sample high or low. Using a static threshold kind of fixes this, but I wish dynamic could return more colors or something.

Uh... I don't really follow, but I'm assuming you mean like... ``` gather colors -> decide sorting method (brightness/salience) -> decide sampling method -> -> decide if post-processing (hard/soft) ``` On the sorting method, earlier colors will always be on the left side (index 0) and later colors will always be on the right side (index 7). If this is what you mean, I think it would be rather elegant. Currently sampling and post-processing are coupled together which makes it weird to make variations from the gathered colors. But you need to be careful on post-processing, because it could alter how colors should be sorted. But that could be fixed with a re-sort or apply the post-processing equally across all colors. Another thing is that we actually might not need to truncate the colors. I know you have a `MAX_COLS=16` assigned set. I think you have this in place for the dynamic thresholding to work? Something I kind of wished is for the dynamic thresholding to generate more colors so that the sampling method has more colors that it could sample from. Like for the `SamplingMode::Distributed`, I like how it spreads out the attention across colors. But when there's not enough colors, it has little to no difference whether we sample high or low. Using a static threshold kind of fixes this, but I wish dynamic could return more colors or something.

@usaradark wrote in #188 (comment):

Uh... I don't really follow, but I'm assuming you mean like...

gather colors -> decide sorting method (brightness/salience) -> decide sampling method ->
-> decide if post-processing (hard/soft)

Yes, I Think sampling and light/dark sorting could be done in the same module, possibly both having direct comunication. Post processing could be done inside palette, so you could just order up the colors or post process it there.

The main issue I found out is about the colorspace -> palette conversion to RGB, since to postprocess you would require to use anything but rgb. Maybe with Traits that can be solved, but that's for future development, maybe for v4.

Something I kind of wished is for the dynamic thresholding to generate more colors so that the sampling method has more colors that it could sample from.

I'm not sure if I follow, but you want something similar, multithreadead that looks up for the "best" threshold, but instead of looking for a good enough threshold, usually the lowest the better (the function looks up until the lowest threshold possible with at least 6 colors), but with salience, so you get like the best "score"?

@usaradark wrote in https://codeberg.org/explosion-mental/wallust/pulls/188#issuecomment-8145509: > Uh... I don't really follow, but I'm assuming you mean like... > > ```text > gather colors -> decide sorting method (brightness/salience) -> decide sampling method -> > -> decide if post-processing (hard/soft) > ``` Yes, I Think sampling and light/dark sorting could be done in the same module, possibly both having direct comunication. Post processing could be done inside `palette`, so you could just order up the colors or post process it there. The main issue I found out is about the colorspace -> palette conversion to RGB, since to postprocess you would require to use anything **but** rgb. Maybe with Traits that can be solved, but that's for future development, maybe for v4. > Something I kind of wished is for the dynamic thresholding to generate more colors so that the sampling method has more colors that it could sample from. I'm not sure if I follow, but you want something similar, multithreadead that looks up for the "best" threshold, but instead of looking for a good enough threshold, usually the lowest the better (the function looks up until the lowest threshold possible with at least 6 colors), but with **salience**, so you get like the best "score"?

BTW, what's missing from this pr? I left some TODOs, but from what I've seen everything else is fine. Other than deleting unused functions like bell and the like (which are maintained in this history in case you revisit or require them)

BTW, what's missing from this pr? I left some TODOs, but from what I've seen everything else is fine. Other than deleting unused functions like `bell` and the like (which are maintained in this history in case you revisit or require them)
Author
Contributor
Copy link

Something I really wanted to do is combine SalienceLight and SalienceDark into one module. If I edit one, I will most certainly have to edit the other. I should be able to get it in a few hours? Shouldn't be too hard, until I shoot myself in the foot. But yeah, after that, that should be it.

edit - Unless you'd prefer to keep them separate for now? I'll start working on it, shouldn't be too difficult and if anything history will be here of the two merged.

Something I really wanted to do is combine `SalienceLight` and `SalienceDark` into one module. If I edit one, I will most certainly have to edit the other. I should be able to get it in a few hours? Shouldn't be too hard, until I shoot myself in the foot. But yeah, after that, that should be it. edit - Unless you'd prefer to keep them separate for now? I'll start working on it, shouldn't be too difficult and if anything history will be here of the two merged.
Author
Contributor
Copy link

That should be it.

I added DistributedSalience as a variant. By default for salience, it samples colors from the center (balanced).

The flavors are for if the user wants...
High -> All colors are not grabbing enough attention, give me more important colors
Distributed -> All colors are all the same attention, spread out the important colors
Low -> All colors are grabbing my attention, give me less important colors

That should be it. I added `DistributedSalience` as a variant. By default for salience, it samples colors from the center (balanced). The flavors are for if the user wants... `High` -> All colors are not grabbing enough attention, give me more important colors `Distributed` -> All colors are all the same attention, spread out the important colors `Low` -> All colors are grabbing my attention, give me less important colors
Author
Contributor
Copy link

I'm not sure if I follow, but you want something similar, multithreadead that looks up for the "best" threshold, but instead of looking for a good enough threshold, usually the lowest the better (the function looks up until the lowest threshold possible with at least 6 colors), but with salience, so you get like the best "score"?

I haven't actually looked at the code yet for dynamic thresholding, but I noticed that when I use it, I get 8+-2 colors, never near the max 16. The above comment explains why having more colors is important.

The amount of colors to work with is essentially equivalent to dynamic range, but for salience (and contrast). When threshold histogram bucketing only results in 6 colors, we have no dynamic range to work with, so the user is forced to deal with their colors, which may be too salient or not salient enough. The contrast also may be too high to comfortably work with. With more colors to initially work with, and with the sampling methods, the user can choose how much salience (and contrast) they want.

Salience and sampling might be able to be used in conjunction with the check_contrast flag, but I think having it stick to standards (web standards or something? i forgot) is probably the way to go.

addition - Even if we integrated salience into checking contrast... maybe it could be part of the filter_cols function. I thought about it, but when the function is ran we have... wait a minute... I can have filter_cols drop the colors that are below a certain threshold from the naive salience function... hmmmmmmmmmmmmmmmm

Let me work on that... I did a quick test and it generates some really interesting things...

> I'm not sure if I follow, but you want something similar, multithreadead that looks up for the "best" threshold, but instead of looking for a good enough threshold, usually the lowest the better (the function looks up until the lowest threshold possible with at least 6 colors), but with salience, so you get like the best "score"? I haven't actually looked at the code yet for dynamic thresholding, but I noticed that when I use it, I get 8+-2 colors, never near the max 16. The above comment explains why having more colors is important. The amount of colors to work with is essentially equivalent to dynamic range, but for salience (and contrast). When threshold histogram bucketing only results in 6 colors, we have no dynamic range to work with, so the user is forced to deal with their colors, which may be too salient or not salient enough. The contrast also may be too high to comfortably work with. With more colors to initially work with, and with the sampling methods, the user can choose how much salience (and contrast) they want. Salience and sampling might be able to be used in conjunction with the `check_contrast` flag, but I think having it stick to standards (web standards or something? i forgot) is probably the way to go. addition - Even if we integrated salience into checking contrast... maybe it could be part of the `filter_cols` function. I thought about it, but when the function is ran we have... wait a minute... I can have `filter_cols` drop the colors that are below a certain threshold from the naive salience function... hmmmmmmmmmmmmmmmm Let me work on that... I did a quick test and it generates some really interesting things...

Salience and sampling might be able to be used in conjunction with the check_contrast flag, but I think having it stick to standards (web standards or something? i forgot) is probably the way to go.

Yes, the WA 3.0 I think it was called, it's been a long time. I noticed I doesn't work that well with a transparent background. I have opacity in my terminal, and there are edge cases in which it requires like a few more iterations (this is very few cases, given that I've tested this out, I found out if I add these few more iterations, for the mayority of the palettes, doesn't suit very well).

I haven't actually looked at the code yet for dynamic thresholding, but I noticed that when I use it, I get 8+-2 colors, never near the max 16. The above comment explains why having more colors is important.

Interesting indeed, the threshold value doesn't always work constantly across images, sometimes higher thresholds gives you way better results, usually tho, is the other way around, less is better. Depends vastly on the image, usually a wallpaper of a landscape, that requires a higher threshold, yet that is still too ambiguous. Maybe salience can help to determine the "perfect threshold"? Interesting ideas indeed, can shape the way I was structuring the proyect, maybe a preset is not in the need as the result (tho, I liked to have the exact results of the generated by pywal, not sure if useful tho).

addition - Even if we integrated salience into checking contrast... maybe it could be part of the filter_cols function. I thought about it, but when the function is ran we have... wait a minute... I can have filter_cols drop the colors that are below a certain threshold from the naive salience function... hmmmmmmmmmmmmmmmm

Yeah, colorspace is indeed very useful to tweak the colors. That's why I think it needs more prominence, at least in the internals.

> Salience and sampling might be able to be used in conjunction with the check_contrast flag, but I think having it stick to standards (web standards or something? i forgot) is probably the way to go. Yes, the WA 3.0 I think it was called, it's been a long time. I noticed I doesn't work that well with a transparent background. I have opacity in my terminal, and there are edge cases in which it requires like a few more iterations (this is very few cases, given that I've tested this out, I found out if I add these few more iterations, for the mayority of the palettes, doesn't suit very well). > I haven't actually looked at the code yet for dynamic thresholding, but I noticed that when I use it, I get 8+-2 colors, never near the max 16. The above comment explains why having more colors is important. Interesting indeed, the threshold value doesn't always work constantly across images, sometimes higher thresholds gives you way better results, usually tho, is the other way around, less is better. Depends vastly on the image, usually a wallpaper of a landscape, that requires a higher threshold, yet that is still too ambiguous. Maybe salience can help to determine the "perfect threshold"? Interesting ideas indeed, can shape the way I was structuring the proyect, maybe a `preset` is not in the need as the result (tho, I liked to have the exact results of the generated by pywal, not sure if useful tho). > addition - Even if we integrated salience into checking contrast... maybe it could be part of the filter_cols function. I thought about it, but when the function is ran we have... wait a minute... I can have filter_cols drop the colors that are below a certain threshold from the naive salience function... hmmmmmmmmmmmmmmmm Yeah, colorspace is indeed very useful to tweak the colors. That's why I think it needs more prominence, at least in the internals.

As of now tho, I think a simple release would do it, integrating some features. I think Im reversing the preset idea as of now.. Given these new options, I'd prefer working on those, after a break ofc.

As of now tho, I think a simple release would do it, integrating some features. I think Im reversing the `preset` idea as of now.. Given these new options, I'd prefer working on those, after a break ofc.

BTW, the comments are very important, since I use those and put them on the documentations

BTW, the comments are very important, since I use those and put them on the documentations
explosion-mental force-pushed salience-v5 from aa28571f3e
Some checks are pending
ci/woodpecker/pr/check Pipeline is pending approval
ci/woodpecker/pr/docs Pipeline is pending approval
ci/woodpecker/pr/nix-build Pipeline is pending approval
to bfc3cfdb17 2025年11月06日 22:39:37 +01:00
Compare
determine bg less aggressively
Some checks are pending
ci/woodpecker/pr/check Pipeline is pending approval
ci/woodpecker/pr/docs Pipeline is pending approval
ci/woodpecker/pr/nix-build Pipeline is pending approval
a9d04c43dd
Author
Contributor
Copy link

Old filtering
2025年11月06日T11:20:59,617559850-08:00

New filtering
2025年11月06日T11:21:51,224362652-08:00

Filtering now removes colors that are below a certain salience threshold for both dark and light themes, essentially removing colors that cannot differentiate well against a naively chosen background. Naive salience doesn't take into account hue... I did it like this initially because we wouldn't know what color to compare against in the end, so I didn't wake to filter out some a hue that might actually be chosen at the end, but maybe naive salience should consider warm/cool colors like my original idea. It would require more testing, but as it is now, it's functional. Test and patch later if it seems good.

It's good enough to be merged at this point. Any other tweaks are fine-tuning functions and models with can be trivially patched.

What's bothering me a lot right now is trying to make the color 1 perceptible from the background, especially in contexts where there's only 6 colors or the user chose a sampling method that chooses the lowest salient color. I thought I could fix this by having a more aggressive filter_cols, but for some reason color 1 would always be of super low salience.

But like, in palette after we've gone through colorspace and therefore filter_cols, I check the lowest salient color and it's a color that SHOULD have been filtered in filter_cols because its l=2 and chroma=4 or something. I'm kind of just baffled on how this color is getting through despite it's calculated naive salience does not satisfy the filter. So it's not really working intended?

If you want to play it safe, you can revert filter_cols in salience.rs and instead use lch.rs's filter_cols, which was what it was using originally. But what we have right now works.

I need to take a break, so I'll be a while before I actually look at the code again. But As mentioned, it's more or less good to be merged. I don't expect to add anything major except maybe an adjustment to color filtering, how bg and col0 is chosen, and maybe some math. But yeah, I don't know how this color is getting through or if the color was transformed in some earlier step or something, brain fried.

If we ever expand on dynamic thresholding to target a higher color count, we could consider a variable sample jump kind of thing. So like instead if sampling [0, 1, 2, 3], we sample [0, 2, 4, 6] or [0, 3, 6, 9]. This would allow the user to better tune what how much contrast they want in salience.

Also, heck, I don't even know what preset even is, lol.

BTW, the comments are very important, since I use those and put them on the documentations

Indeed. I don't know how much commenting you want, but considering that you like to shorten variables and the like, I did similar with extra comments to elaborate on what's going on. If there's anything you want me to document some more just let me know and I can try to explain it in simple but still technical-enough ways.

Old filtering ![2025年11月06日T11:20:59,617559850-08:00](/attachments/a64c1d85-6d3b-4824-9c48-060dd0f64966) New filtering ![2025年11月06日T11:21:51,224362652-08:00](/attachments/d24ca4f2-2ee4-4745-a32f-9f2d86d76309) Filtering now removes colors that are below a certain salience threshold for both dark and light themes, essentially removing colors that cannot differentiate well against a naively chosen background. Naive salience doesn't take into account hue... I did it like this initially because we wouldn't know what color to compare against in the end, so I didn't wake to filter out some a hue that might actually be chosen at the end, but maybe naive salience should consider warm/cool colors like my original idea. It would require more testing, but as it is now, it's functional. Test and patch later if it seems good. It's good enough to be merged at this point. Any other tweaks are fine-tuning functions and models with can be trivially patched. What's bothering me a lot right now is trying to make the `color 1` perceptible from the background, especially in contexts where there's only 6 colors or the user chose a sampling method that chooses the lowest salient color. I thought I could fix this by having a more aggressive `filter_cols`, but for some reason `color 1` would always be of super low salience. But like, in `palette` after we've gone through `colorspace` and therefore `filter_cols`, I check the lowest salient color and it's a color that SHOULD have been filtered in `filter_cols` because its `l=2` and `chroma=4` or something. I'm kind of just baffled on how this color is getting through despite it's calculated naive salience does not satisfy the filter. So it's not really working intended? If you want to play it safe, you can revert `filter_cols` in `salience.rs` and instead use `lch.rs`'s `filter_cols`, which was what it was using originally. But what we have right now *works*. I need to take a break, so I'll be a while before I actually look at the code again. But As mentioned, it's more or less good to be merged. I don't expect to add anything major except maybe an adjustment to color filtering, how `bg` and `col0` is chosen, and maybe some math. But yeah, I don't know how this color is getting through or if the color was transformed in some earlier step or something, brain fried. If we ever expand on dynamic thresholding to target a higher color count, we could consider a variable sample jump kind of thing. So like instead if sampling [0, 1, 2, 3], we sample [0, 2, 4, 6] or [0, 3, 6, 9]. This would allow the user to better tune what how much contrast they want in salience. Also, heck, I don't even know what `preset` even is, lol. > BTW, the comments are very important, since I use those and put them on the documentations Indeed. I don't know how much commenting you want, but considering that you like to shorten variables and the like, I did similar with extra comments to elaborate on what's going on. If there's anything you want me to document some more just let me know and I can try to explain it in simple but still technical-enough ways.
Author
Contributor
Copy link

I think I found the bug, but I'll probably push it tomorrow before I do anything too reckless. I think it's because I overwrote the background color too early and it's doing some weird things.

I think I found the bug, but I'll probably push it tomorrow before I do anything too reckless. I think it's because I overwrote the background color too early and it's doing some weird things.

Also, heck, I don't even know what preset even is, lol.
Yeah, forget that, don't fry more ur brain.

If we ever expand on dynamic thresholding to target a higher color count, we could consider a variable sample jump kind of thing. So like instead if sampling [0, 1, 2, 3], we sample [0, 2, 4, 6] or [0, 3, 6, 9]. This would allow the user to better tune what how much contrast they want in salience.

This is interesting. Would require to look up how much influence has the threshold on salience values, since the threshold is for determined how "similar" are each color from another, since it's a perceptual thing it is a scale.

And, as for the pics, somehow I prefer the old filtering sorting, but I do agree that the new filtering has much more contrast, more readable.

Indeed. I don't know how much commenting you want, but considering that you like to shorten variables and the like, I did similar with extra comments to elaborate on what's going on. If there's anything you want me to document some more just let me know and I can try to explain it in simple but still technical-enough ways.

Yeah, I think you nailed it on there. But I was referring that the comments from each enum element either from colorspace, backend or palette, is moved automagically into the documentation for the site, so you take it into account. For example, look the Color Space section. (this is done with make update-docs btw, I think I could add it at the end)

> Also, heck, I don't even know what preset even is, lol. Yeah, forget that, don't fry more ur brain. > If we ever expand on dynamic thresholding to target a higher color count, we could consider a variable sample jump kind of thing. So like instead if sampling [0, 1, 2, 3], we sample [0, 2, 4, 6] or [0, 3, 6, 9]. This would allow the user to better tune what how much contrast they want in salience. This is interesting. Would require to look up how much influence has the threshold on salience values, since the threshold is for determined how "similar" are each color from another, since it's a perceptual thing it is a scale. And, as for the pics, somehow I prefer the `old filtering` sorting, but I do agree that the `new filtering` has much more contrast, more readable. > Indeed. I don't know how much commenting you want, but considering that you like to shorten variables and the like, I did similar with extra comments to elaborate on what's going on. If there's anything you want me to document some more just let me know and I can try to explain it in simple but still technical-enough ways. Yeah, I think you nailed it on there. But I was referring that the comments from each enum element either from colorspace, backend or palette, is moved automagically into the documentation for the site, so you take it into account. For example, look the [Color Space section](https://explosion-mental.codeberg.page/wallust/parameters/colorspace.html). (this is done with `make update-docs` btw, I think I could add it at the end)
finalize sal calcs, sal bg strategy, consts
Some checks failed
ci/woodpecker/pr/check Pipeline was successful
ci/woodpecker/pr/docs Pipeline failed
ci/woodpecker/pr/nix-build Pipeline was successful
e763a35e2b
Author
Contributor
Copy link

I'M DONE!

After much testing on various background, I can with 90% confidence say that the entirety of colors are sorted correctly, even colors on the most extreme end of salience. This means that distributedsalience and lowsalience are actually viable now (was having a hard time finetuning the lower-extreme saliences). I prefer distributedsalience as my default, but sampling high (the current default implementation of on salience) is a safe pick for users to prevent col1 from being difficult to perceive. But even with coded defaults, I can perceive it comfortably.

I would suggest doing some tests and decide what the default should be (high, distributed, balanced (centered), low). Or just force the user to specify the sampling method (make salience into highsalience).

Also added better(?) documentation for the enums you mentioned. Not too sure if there's anything else, but if all is good, it's ready to merge. I don't have anything else to implement or fix...for now part 3

Here's some demos of lch + lchdark16 vs salience + saliencedark16 vs salience + distributedsaliencedark16. If you use Firefox, I noticed transparency shows up as white. I would suggest installing this plugin to force transparency to match the browser background. They way to can compare the images a bit better without the white flashbang.


lch + lchdark16 (current)
2025年11月07日T18:41:11,285323870-08:00
salience + saliencedark16 (proposed)
2025年11月07日T18:41:19,994565199-08:00
salience + distributedsaliencedark16 (proposed)
2025年11月07日T18:41:34,923560957-08:00


lch + lchdark16 (current)
2025年11月07日T18:41:46,949769104-08:00
salience + saliencedark16 (proposed)
2025年11月07日T18:41:56,483695797-08:00
salience + distributedsaliencedark16 (proposed)
2025年11月07日T18:42:05,183728168-08:00


lch + lchdark16 (current)
2025年11月07日T18:42:25,829332934-08:00
salience + saliencedark16 (proposed)
2025年11月07日T18:42:37,683626406-08:00
salience + distributedsaliencedark16 (proposed)
2025年11月07日T18:42:49,653385396-08:00

## I'M DONE! After much testing on various background, I can with 90% confidence say that the entirety of colors are sorted correctly, even colors on the most extreme end of salience. This means that `distributedsalience` and `lowsalience` are actually viable now (was having a hard time finetuning the lower-extreme saliences). I prefer `distributedsalience` as my default, but sampling high (the current default implementation of on `salience`) is a safe pick for users to prevent `col1` from being difficult to perceive. But even with coded defaults, I can perceive it comfortably. I would suggest doing some tests and decide what the default should be (`high`, `distributed`, `balanced (centered)`, `low`). Or just force the user to specify the sampling method (make `salience` into `highsalience`). Also added better(?) documentation for the enums you mentioned. Not too sure if there's anything else, but if all is good, it's ready to merge. I don't have anything else to implement or fix...<sup>*for now part 3*</sup> Here's some demos of `lch + lchdark16` vs `salience + saliencedark16` vs `salience + distributedsaliencedark16`. If you use Firefox, I noticed transparency shows up as white. I would suggest installing [this](https://addons.mozilla.org/en-US/firefox/addon/transparent-standalone-image/) plugin to force transparency to match the browser background. They way to can compare the images a bit better without the white flashbang. --- lch + lchdark16 (current) ![2025年11月07日T18:41:11,285323870-08:00](/attachments/bf56bd98-b755-4def-984a-509700f91828) salience + saliencedark16 (proposed) ![2025年11月07日T18:41:19,994565199-08:00](/attachments/e3a0505b-9ec0-48d9-87ea-b7734746bb62) salience + distributedsaliencedark16 (proposed) ![2025年11月07日T18:41:34,923560957-08:00](/attachments/5cccd38c-c9e6-4d5a-a1d2-5522f86a8531) --- lch + lchdark16 (current) ![2025年11月07日T18:41:46,949769104-08:00](/attachments/c8a63ff1-1676-41ec-84f8-8ba3c6ea87f7) salience + saliencedark16 (proposed) ![2025年11月07日T18:41:56,483695797-08:00](/attachments/cfc8b848-8f63-4d18-93f3-44f28bd4a461) salience + distributedsaliencedark16 (proposed) ![2025年11月07日T18:42:05,183728168-08:00](/attachments/fa6c0924-ddb3-4f96-852c-53157ac20dba) --- lch + lchdark16 (current) ![2025年11月07日T18:42:25,829332934-08:00](/attachments/5ee37e91-adca-4641-b31b-a38eff4abcbf) salience + saliencedark16 (proposed) ![2025年11月07日T18:42:37,683626406-08:00](/attachments/32386855-358a-4eb9-b53c-e4d2a416c96d) salience + distributedsaliencedark16 (proposed) ![2025年11月07日T18:42:49,653385396-08:00](/attachments/4e376b23-2a9c-4539-b243-df17adcc938b)
explosion-mental changed title from (削除) WIP: Gather and sort colors by salience instead of brightness (削除ここまで) to Gather and sort colors by salience instead of brightness 2025年11月08日 17:00:27 +01:00
Assign Balanced to Balanced, not High
Some checks are pending
ci/woodpecker/pr/check Pipeline is pending approval
ci/woodpecker/pr/docs Pipeline is pending approval
ci/woodpecker/pr/nix-build Pipeline is pending approval
1dea6a0d8b
Resolved alrd, used by salience that requires a process in between,
before truncation but after dedup.
update assets
All checks were successful
ci/woodpecker/pr/check Pipeline was successful
ci/woodpecker/pr/docs Pipeline was successful
ci/woodpecker/pr/nix-build Pipeline was successful
f02a15b980

I think it's ready :], I'm going to test it for a couple days and prob squash it soon, wonderful work!

The rename was to maintain some kind of convention, also helps with completion by editing or selecting in the shell :p

I think it's ready :], I'm going to test it for a couple days and prob squash it soon, wonderful work! The rename was to maintain some kind of convention, also helps with completion by editing or selecting in the shell :p
Author
Contributor
Copy link

Thanks for the patience and help. I'll also be working to better the salience calculation in the coming days/weeks to let some ideas ferment. It's a strange balance where hue depends on chroma, and chroma depends on luminance. I'm mostly happy with what's here right now; a few colors out of place but it's acceptable.

Let me know of any rather extreme oddities and suggestions. Right now I'm fundamentally trying to reason each component of LCH to better map them to salience. Before it was a lot of guesswork and intuition, but to get a more accurate model is going to require better fundamentals.

Thanks for the patience and help. I'll also be working to better the salience calculation in the coming days/weeks to let some ideas ferment. It's a strange balance where hue depends on chroma, and chroma depends on luminance. I'm mostly happy with what's here right now; a few colors out of place but it's acceptable. Let me know of any rather extreme oddities and suggestions. Right now I'm fundamentally trying to reason each component of LCH to better map them to salience. Before it was a lot of guesswork and intuition, but to get a more accurate model is going to require better fundamentals.
Author
Contributor
Copy link

What are your thoughts on what the saliency of colors col7, col8, and col15 should be in relation to the saliency of other colors? Right now I think for dark theme it lightens it, which increases (rarely reduces) saliency, meaning it would be the those colors would pop out the most.

I'm under the impression that col7 (white) is meant to be more neutral in attention, and might be better if its saliency is adjusted as some median of all colors? As in it neither draws nor dismisses attention. col15 should probably draw the most attention of all the colors. col7... maybe something similar to median, but below col7?

What are your thoughts on what the saliency of colors `col7`, `col8`, and `col15` should be in relation to the saliency of other colors? Right now I think for dark theme it lightens it, which increases (rarely reduces) saliency, meaning it would be the those colors would pop out the most. I'm under the impression that `col7` (white) is meant to be more neutral in attention, and might be better if its saliency is adjusted as some median of all colors? As in it neither draws nor dismisses attention. `col15` should probably draw the most attention of all the colors. `col7`... maybe something similar to median, but below `col7`?
bugfix: remove from lchs, not cols; lchs is used to pick high, not cols
All checks were successful
ci/woodpecker/pr/check Pipeline was successful
ci/woodpecker/pr/docs Pipeline was successful
ci/woodpecker/pr/nix-build Pipeline was successful
5633e50340
Author
Contributor
Copy link

I yet again have a much improved salience model calculation thing in the works. I'll do some more testing and probably push it within (削除) the next few hours (削除ここまで) a few days, but it looks a lot better (I'm pretty sure I've said that too many times this thread).

Fixes cases like this, where there's a saturated dark color.

Current
2025年11月09日T19:45:36,769439138-08:00


New
2025年11月09日T19:45:52,380444574-08:00


Current
2025年11月09日T20:40:18,017906666-08:00


New
2025年11月09日T20:40:32,125555056-08:00


edit - better example
edit2 - another example
edit3 - found another weird edge case, working on it...

I yet again have a much improved salience model calculation thing in the works. I'll do some more testing and probably push it within ~~the next few hours~~ a few days, but it looks a lot better (I'm pretty sure I've said that too many times this thread). Fixes cases like this, where there's a saturated dark color. Current ![2025年11月09日T19:45:36,769439138-08:00](/attachments/88a0385a-b35a-436a-ab85-55032559458a) --- New ![2025年11月09日T19:45:52,380444574-08:00](/attachments/77a1cb27-bbda-4391-b4b9-907cccb7bc3f) --- Current ![2025年11月09日T20:40:18,017906666-08:00](/attachments/72feebed-6e3a-4b83-9079-bc933b10a901) --- New ![2025年11月09日T20:40:32,125555056-08:00](/attachments/15b87202-1049-455f-8ddb-b5c9de69ece3) --- edit - better example edit2 - another example edit3 - found another weird edge case, working on it...
Author
Contributor
Copy link

Is there a reason why sorting by palette::ColorDifference was never considered...? The more I play with LCH the more I learn that it just isn't a good colorspace for mapping salience, at least with my naive model. The range of chroma values vary depending on the hue, making it near impossible to consistently score chroma+hue (and chroma itself because it's bound by hue) for saliency contribution.

Currently experimenting with ImprovedCiede2000, which from what I'm reading does heavy corrections and adjustments to LAB.

Is there a reason why sorting by palette::ColorDifference was never considered...? The more I play with LCH the more I learn that it just isn't a good colorspace for mapping salience, at least with my naive model. The range of chroma values vary depending on the hue, making it near impossible to consistently score chroma+hue (and chroma itself because it's bound by hue) for saliency contribution. Currently experimenting with ImprovedCiede2000, which from what I'm reading does heavy corrections and adjustments to LAB.
Author
Contributor
Copy link

I'm just going to put this here...

https://codeberg.org/usaradark/wallust/src/branch/cam16

image
image
image
image

edit addon: Implementing Cam16UcsJmh has been... interesting... definitely learning a LOT of stuff and I need more time to digest what the heck I'm actually doing. But! Progress is most definitely being made. I would consider not merging salience-v5 and trying out cam16. It's a bit rough, but it sorts colors MUCH more accurately. Currently doesn't consider biological hues (i.e. red colors grab more attention than blues), but it might not even be needed if I can get this working properly.

I'm just going to put this here... https://codeberg.org/usaradark/wallust/src/branch/cam16 ![image](/attachments/97f9c050-167e-4b25-9001-d369ef94edcb) ![image](/attachments/f160dbc2-d4b2-4cd2-aac8-441c35c936fc) ![image](/attachments/d65a0306-0069-4e3e-8467-1847eaee5f37) ![image](/attachments/4da451c0-ed2d-499d-8622-27e403902143) edit addon: Implementing `Cam16UcsJmh` has been... interesting... definitely learning a LOT of stuff and I need more time to digest what the heck I'm actually doing. But! Progress is most definitely being made. I would consider not merging `salience-v5` and trying out `cam16`. It's a bit rough, but it sorts colors MUCH more accurately. Currently doesn't consider biological hues (i.e. red colors grab more attention than blues), but it might not even be needed if I can get this working properly.
Author
Contributor
Copy link

For lch, what was your reasoning for using ImprovedCiede2000 over Ciede20000 for the Difference trait? Or in other words, why did you decide to gather and dedup colors with differences scaled to human perception rather than ground truth? ImprovedCiede2000 has power law scaling such that the deltas diminish after some point, similar to how psychological differences diminish once we see something as different. I"m just a bit confused on when to use one other the other.

For `lch`, what was your reasoning for using `ImprovedCiede2000` over `Ciede20000` for the `Difference` trait? Or in other words, why did you decide to gather and dedup colors with differences scaled to human perception rather than ground truth? `ImprovedCiede2000` has power law scaling such that the deltas diminish after some point, similar to how psychological differences diminish once we see something as different. I"m just a bit confused on when to use one other the other.

@usaradark wrote in #188 (comment):

For lch, what was your reasoning for using ImprovedCiede2000 over Ciede20000 for the Difference trait? Or in other words, why did you decide to gather and dedup colors with differences scaled to human perception rather than ground truth? ImprovedCiede2000 has power law scaling such that the deltas diminish after some point, similar to how psychological differences diminish once we see something as different. I"m just a bit confused on when to use one other the other.

In this case I was basing my decision on some personal tests, leading to the fastest one to load. Even then, I think I used a simple euclidean formula, which I think it's the CIEDE.

Edit: Maybe the Difference Trait can take an approach similar to BuildHisto, in which it uses a custom type, isntead of requiring the palette type, in order to make it possible to define it in each module, instead only at different [palette (the crate)] types. In case you need to define or change the difference formula. (e0549c1afb)

@usaradark wrote in https://codeberg.org/explosion-mental/wallust/pulls/188#issuecomment-8274845: > For `lch`, what was your reasoning for using `ImprovedCiede2000` over `Ciede20000` for the `Difference` trait? Or in other words, why did you decide to gather and dedup colors with differences scaled to human perception rather than ground truth? `ImprovedCiede2000` has power law scaling such that the deltas diminish after some point, similar to how psychological differences diminish once we see something as different. I"m just a bit confused on when to use one other the other. In this case I was basing my decision on some personal tests, leading to the fastest one to load. Even then, I think I used a simple euclidean formula, which I think it's the CIEDE. Edit: Maybe the `Difference` Trait can take an approach similar to `BuildHisto`, in which it uses a custom type, isntead of requiring the `palette` type, in order to make it possible to define it in each module, instead only at different [palette (the crate)] types. In case you need to define or change the difference formula. (e0549c1afb87e710599e9849e5b6bceb75d55ce1)
Author
Contributor
Copy link

If speed is a concern, Cam16UcsJmh differences is just the root sum of squares (distance), so calculation is rather trivial.

https://docs.rs/palette/0.7.6/src/palette/cam16/ucs_jab.rs.html#209-211
https://docs.rs/palette/0.7.6/src/palette/cam16/ucs_jab.rs.html#220-228

The concern would be potential overhead for creating and inferencing the Cam16 object (specifically the Cam16UcsJmh).


The reason why I asked is that with the Improved version, differences don't scale linearly due to power-law scaling adjustments by Huang et al. The deltas taper off and soft-cap around 20 (at least for ImprovedDeltaE for Cam16UcsJmh). This is fine because the adjustments are meant to model human perception of differences (differences have diminishing returns to perception). However, thresholding currently doesn't take into account this scaling and only scales linearly, causing a mismatch in interface. It's this mismatch that's confusing and blocking me.

Do we base calculation based on physical differences DeltaE or perceptual differences ImprovedDeltaE? Either or will work, we just need to adjust(?) the way thresholding (and else?) is approached. The other thing, too, is that for whichever one we choose, it shouldn't change the results (like sorting) so long as it's the correct threshold; it's merely an interface approach.

Because of this mismatch, Lch which uses ImprovedCiede2000, so thresholding beyond that softcap (~26) is meaningless, resulting in little to one color. Dynamic thresholding works linearly, so any searching beyond the softcap is meaningless, and where it starts (20) is already near max deltas, resulting in not many colors being gathered to begin with.

If we wanted align threshold and deltas, we would have to revert back to regular DeltaE so it's linear like threshold, or scale threshold on a per-implementation basis (Lch has different scalings than Cam16). The latter would provide a more consistent results across colorspaces as now everything scales to perception.

But like, it depends on what threshold is meant to ask and what it's used for. If threshold is meant to define "the point at which these two colors are physically different", linear scaling should be used, but if "the point at which these two colors are perceptually different", then power-law scaling (Improved Huang et al.) should be used. And the use case for threshold is for gathering colors and deduplicating... results should be the same with appropriate thresholds.

edit - There's been a lot of discussion of patches and tweaks, such as #189 and this consideration of changes to our approach to color differences and threshold. The scope of this pull request is potentially getting large. I'm not too sure how you want to proceed, but for now I'll just try to keep this Salience to keep things organized.

If speed is a concern, Cam16UcsJmh differences is just the root sum of squares (distance), so calculation is rather trivial. https://docs.rs/palette/0.7.6/src/palette/cam16/ucs_jab.rs.html#209-211 https://docs.rs/palette/0.7.6/src/palette/cam16/ucs_jab.rs.html#220-228 The concern would be potential overhead for creating and inferencing the Cam16 object (specifically the `Cam16UcsJmh`). --- The reason why I asked is that with the Improved version, differences don't scale linearly due to power-law scaling adjustments by Huang et **al.** The deltas taper off and soft-cap around 20 (at least for `ImprovedDeltaE` for `Cam16UcsJmh`). This is fine because the adjustments are meant to model human perception of differences (differences have diminishing returns to perception). However, thresholding currently doesn't take into account this scaling and only scales linearly, causing a mismatch in interface. It's this mismatch that's confusing and blocking me. **Do we base calculation based on physical differences `DeltaE` or perceptual differences `ImprovedDeltaE`?** Either or will work, we just need to adjust(?) the way thresholding (and else?) is approached. The other thing, too, is that for whichever one we choose, it shouldn't change the results (like sorting) so long as it's the correct threshold; it's merely an interface approach. Because of this mismatch, Lch which uses `ImprovedCiede2000`, so thresholding beyond that softcap (~26) is meaningless, resulting in little to one color. Dynamic thresholding works linearly, so any searching beyond the softcap is meaningless, and where it starts (20) is already near max deltas, resulting in not many colors being gathered to begin with. If we wanted align threshold and deltas, we would have to revert back to regular `DeltaE` so it's linear like threshold, or scale threshold on a per-implementation basis (Lch has different scalings than Cam16). The latter would provide a more consistent results across colorspaces as now everything scales to perception. But like, it depends on what threshold is meant to ask and what it's used for. If threshold is meant to define *"the point at which these two colors are physically different"*, linear scaling should be used, but if *"the point at which these two colors are perceptually different"*, then power-law scaling (`Improved` Huang et al.) should be used. And the use case for threshold is for gathering colors and deduplicating... results should be the same with appropriate thresholds. edit - There's been a lot of discussion of patches and tweaks, such as #189 and this consideration of changes to our approach to color differences and threshold. The scope of this pull request is potentially getting large. I'm not too sure how you want to proceed, but for now I'll just try to keep this Salience to keep things organized.

I first started using the LAB colorspace. Working with basing the color logic as the pywal one I saw that it used HSV, I somehow found out CIELAB is "better" for overall perception AND calculations (nothing theorical yet, just saw a few comments on some old threads). Coming from a simple philosophy of code base, first writting in C, I didn't want much libraries but the one that reads the image. So, at the time I was mostly writting everything by hand. The threshold thing I got it was from here and by a friend that suggested to use a simple euclidean calculation. I remember I had two implementations, the 2000s one was giving good results at the cost of speed, while the 94 was much more faster. Now, mind that at this time, there wasn't the fastresize backend, so I mostly was testing with full or resized, which are still "slow" in these operations given the number of pixels. I think I got inspired by this impl at first.

As for the scope of the PR, let's close the salience thing. If you think that UCAM can be used, as in it doesn't change much from the prior impl codewise while implementing much better results, I think you should push it. Restructuring the " MIDDLE " process should be in another thread/PR that we can discuss to properly rewrite the code. This is to release a version with this, get some feedback (if any :p) before the internals major restructure, given that it prob will affect the way users interact with it. (also to release a version kek)

About that, I think it can mostly mantain the trait based model and change implementations, changing the focus from colorspaces to HISTOGRAM gatherings. In this process, I think the first step, before deleting/modifing directly the colorspaces, is creating another module for the Histogram, using the bucket or count impls. My main challange with histogram was the generics, which I fixed with the ColorTrait.

I first started using the LAB colorspace. Working with basing the color logic as the pywal one I saw that it used HSV, I somehow found out CIELAB is "better" for overall perception AND calculations (nothing theorical yet, just saw a few comments on some old threads). Coming from a simple philosophy of code base, first writting in C, I didn't want much libraries but the one that reads the image. So, at the time I was mostly writting everything by hand. The threshold thing I got it was from [here](https://zschuessler.github.io/DeltaE/learn/) and by a friend that suggested to use a simple euclidean calculation. I remember I had two implementations, the 2000s one was giving good results at the cost of speed, while the 94 was much more faster. Now, mind that at this time, there wasn't the `fastresize` backend, so I mostly was testing with `full` or `resized`, which are still "slow" in these operations given the number of pixels. I think I got inspired by [this](https://github.com/elliotekj/DeltaE) impl at first. As for the scope of the PR, let's close the salience thing. If you think that UCAM can be used, as in it doesn't change much from the prior impl codewise while implementing much better results, I think you should push it. Restructuring the " `MIDDLE` " process should be in another thread/PR that we can discuss to properly rewrite the code. This is to release a version with this, get some feedback (if any :p) before the internals major restructure, given that it prob will affect the way users interact with it. (also to release a version kek) About that, I think it can mostly mantain the trait based model and change implementations, changing the focus from colorspaces to HISTOGRAM gatherings. In this process, I think the first step, before deleting/modifing directly the colorspaces, is creating another module for the Histogram, using the bucket or count impls. My main challange with histogram was the generics, which I fixed with the `ColorTrait`.
Author
Contributor
Copy link

I'm finally free tomorrow, so I will work on cleaning up the Cam16 implementation. It's quite different, but much simpler than the original, so there shouldn't be too much new code to go through.

I'm finally free tomorrow, so I will work on cleaning up the Cam16 implementation. It's quite different, but much simpler than the original, so there shouldn't be too much new code to go through.
Author
Contributor
Copy link
I'm happy with this. ![2025年11月16日T19:51:22,209943811-08:00](/attachments/0671c479-b33d-41e3-b402-b7b73c087c25) ![2025年11月16日T19:49:44,256932341-08:00](/attachments/c8b96740-7285-4e55-b308-2efa773a873c) ![image](/attachments/12d1c9c7-9225-491e-8ece-aef6693ce4cd)
Author
Contributor
Copy link

Small problem where I don't know here to put a single saturated color in a sea of low saturation, but after some more testing it should be a trivial patch with what's in place, no overly complex math, just a single weight.

Cam16UcsJmh does not trivially convert from itself to other colors, requiring Parameters to convert. To get it working I had to create a newtype wrapper and implement traits for ColorTrait. To prevent excessive computation on conversion, Parameters can be baked into BakedParameters. This is stored on a global variable to prevent recomputation of Parameters and to not excessively alter the main code, which would have to be done if I passed it around as a parameter. It would also be rather messy.

I split the constants defined up on top into constants that would alter colors gathered and how the background would be generated. I found this gave more intuitive control over contrast, especially when sampling colors from lower salience. What's in place is supposed to prevent the background from turning pitch black, we want to see some background color so we can perceive complementary contrast. Biological hues are currently not considered as complementary is meant to dominate pretty much as much as possible.

I had some thoughts on scaling the background color with the overall luminance of the colors generated, kind of like how you generated background for dark. It's probably possible... I just haven't worked on that feature right now. Right now it scales from dark to near pitch black depending on how much it needs to scale back to allow the least salient color to be visible.

Something interesting I managed to do is to generate the background early to get more accurate sorting and truncation in the colorspace module before passing it to palette. It allows for a more informed clipping process, clipping colors that are too close to the background.

I get a few issues where some seemingly random colors appear in the palette. This is because the histogram doesn't truncate extremely low color counts #189. I didn't touch it for now...

Small problem where I don't know here to put a single saturated color in a sea of low saturation, but after some more testing it should be a trivial patch with what's in place, no overly complex math, just a single weight. `Cam16UcsJmh` does not trivially convert from itself to other colors, requiring Parameters to convert. To get it working I had to create a newtype wrapper and implement traits for ColorTrait. To prevent excessive computation on conversion, Parameters can be baked into BakedParameters. This is stored on a global variable to prevent recomputation of Parameters and to not excessively alter the main code, which would have to be done if I passed it around as a parameter. It would also be rather messy. I split the constants defined up on top into constants that would alter colors gathered and how the background would be generated. I found this gave more intuitive control over contrast, especially when sampling colors from lower salience. What's in place is supposed to prevent the background from turning pitch black, we want to see some background color so we can perceive complementary contrast. Biological hues are currently not considered as complementary is meant to dominate pretty much as much as possible. I had some thoughts on scaling the background color with the overall luminance of the colors generated, kind of like how you generated background for `dark.` It's probably possible... I just haven't worked on that feature right now. Right now it scales from dark to near pitch black depending on how much it needs to scale back to allow the least salient color to be visible. Something interesting I managed to do is to generate the background early to get more accurate sorting and truncation in the colorspace module before passing it to palette. It allows for a more informed clipping process, clipping colors that are too close to the background. I get a few issues where some seemingly random colors appear in the palette. This is because the histogram doesn't truncate extremely low color counts #189. I didn't touch it for now...

Something interesting I managed to do is to generate the background early to get more accurate sorting and truncation in the colorspace module before passing it to palette. It allows for a more informed clipping process, clipping colors that are too close to the background.

I get a few issues where some seemingly random colors appear in the palette. This is because the histogram doesn't truncate extremely low color counts #189. I didn't touch it for now...

I think this could be improved also in the histogram phase, as it should generate a background and foreground colors in correlation to the image, but not precise. I think it can be generated there, maybe a color pixel from the image is just perfect, or maybe none satisfies the contrast, so we require it to tweak it. This correlates to an idea I had, a variable in the templates that determines whether the image is "dark or light", as in, does it leans to darker colors or more pastel lighter ones? This is kinda arbitrary at some images, in which I think there should be a "default" or "fallback" variable that can be defined. And like you said, these changes can be made by first restructuring the histogram mechanism.

I split the constants defined up on top into constants that would alter colors gathered and how the background would be generated. I found this gave more intuitive control over contrast, especially when sampling colors from lower salience. What's in place is supposed to prevent the background from turning pitch black, we want to see some background color so we can perceive complementary contrast. Biological hues are currently not considered as complementary is meant to dominate pretty much as much as possible.

Now that you mention about contrast, I must ask, given the thoughts process you have on developing these new modules, you think the contrast process should be in the internals (histogram/colorspaces) or is it fine as an external process? I think the later was the simpler option, but now that the restructuring thing is in the [mental] works, would help your comment on it.

As for the examples you showed, I've been using the salience impl for like some days now, and I did have a bit of issues about the color1 difference with color0 or the background. However, it wasn't that constant, like 30-40%ish of the time. I'm updating to the cam impl and to continue some testing.

> Something interesting I managed to do is to generate the background early to get more accurate sorting and truncation in the colorspace module before passing it to palette. It allows for a more informed clipping process, clipping colors that are too close to the background. > >I get a few issues where some seemingly random colors appear in the palette. This is because the histogram doesn't truncate extremely low color counts #189. I didn't touch it for now... I think this could be improved also in the histogram phase, as it should generate a background and foreground colors in correlation to the image, but not precise. I think it can be generated there, maybe a color pixel from the image is just perfect, or maybe none satisfies the contrast, so we require it to tweak it. This correlates to an idea I had, a variable in the templates that determines whether the image is "dark or light", as in, does it leans to darker colors or more pastel lighter ones? This is kinda arbitrary at some images, in which I think there should be a "default" or "fallback" variable that can be defined. And like you said, these changes can be made by first restructuring the histogram mechanism. > I split the constants defined up on top into constants that would alter colors gathered and how the background would be generated. I found this gave more intuitive control over contrast, especially when sampling colors from lower salience. What's in place is supposed to prevent the background from turning pitch black, we want to see some background color so we can perceive complementary contrast. Biological hues are currently not considered as complementary is meant to dominate pretty much as much as possible. Now that you mention about contrast, I must ask, given the thoughts process you have on developing these new modules, you think the contrast process should be in the internals (histogram/colorspaces) or is it fine as an external process? I think the later was the simpler option, but now that the restructuring thing is in the [mental] works, would help your comment on it. As for the examples you showed, I've been using the salience impl for like some days now, and I did have a bit of issues about the `color1` difference with `color0` or the background. However, it wasn't that constant, like 30-40%ish of the time. I'm updating to the cam impl and to continue some testing.
Author
Contributor
Copy link

@explosion-mental wrote in #188 (comment):

I did have a bit of issues about the color1 difference with color0 or the background. However, it wasn't that constant, like 30-40%ish of the time.

Between two colors, when luminance is equal but chroma differs, it can result in a salience value big enough to bypass the filter_cols. This is partially because salience was calculated as a weight sum, so chroma acts independently from luminance, but in actuality, chroma depends on luminance. So instead of l + c + h it actually should have been more like l * c * h or something like that. I experimented with the latter and it was much better for the Lch implementation, but I don't think I ever pushed it.

I since moved to Cam16. It should not have this problem because the chroma dependence on luminance is decoupled into colorfulness. In other words, colorfulness is meant to act completely independent from lightness (luminance). Here is a color picker for demonstration purposes.

https://apps.colorjs.io/picker/cam16-jmh

It's kind of crazy how it's decoupled. It very easy allows you to pick colors that straight up cannot be displayed, but the deltas are accurate and decoupled. So in this case, a weight sum, or in this case, the sqrt of sums squared, works rather elegantly.

But... the issue of equal lightness but differing colorfulness still goes through (e.g. l=5, m=5 vs l=5, m=20, m is colorfulness). This has rather poor contrast, which is to suggest that lightness still dominates colorfulness in salience (or at least the range for colorfulness is very big, it's actually infinite). For now I just did a hard constraint that there must be a delta lightness of some value between the background and col1. This could probably be fixed by adjusting the weights, which I have done but haven't actually tested without that hard constraint. Could be double patched right now. Regardless, that issue should be no more... hopefully.

If you want colors closer to the background, as in less contrast, you may need to lower thresholding to not join colors too much to allow the lower salient ones to come through, but not so low that they get clipped.

Now that you mention about contrast, I must ask, given the thoughts process you have on developing these new modules, you think the contrast process should be in the internals (histogram/colorspaces) or is it fine as an external process? I think the later was the simpler option, but now that the restructuring thing is in the [mental] works, would help your comment on it.

That's interesting. I always had in the back of my head that it was odd that I didn't need the check contrast flag because it just worked for my image sets. To me, if the contrast wasn't good AND we are at a pure black background, it means the colors the filter was too narrow, colors were too aggressively merged, or straight up the provided image is not good. I haven't tried how robust the implementation is, but I'd assume given a very dark image with very dark colors and very few of them, it would struggle to capture a palette.

So it's possible the external contrast check is a patch for oddities in how the background is created or/and how the colors are picked/merged. It could probably be fixed internally with some adjustments to DARKEST and/or LIGHTEST or constraints against an early generated background color.

I think this could be improved also in the histogram phase, as it should generate a background and foreground colors in correlation to the image, but not precise. I think it can be generated there, maybe a color pixel from the image is just perfect, or maybe none satisfies the contrast, so we require it to tweak it.

I'm a bit confused on what you're brainstorming... but, I know in #189 it was odd to pass colors whose counts are in the single digits. There's times where I got count 1 from the histogram and it passed to the palette section. If we implement #189, we could have a fallback where the constraint for clipping low counts is removed.

I also tried my best not to alter the colors themselves (though viewing Parameters does things), so to me this is the complete last resort if we've gone through all the thresholds and we still can't get good contrast. If we get to that point, it means the image has poor contrast to begin with. There's no way to use the colors as is and have good contrast, so we HAVE to alter them. I think I got that from the second half of the quoted statement above.

This correlates to an idea I had, a variable in the templates that determines whether the image is "dark or light", as in, does it leans to darker colors or more pastel lighter ones? This is kinda arbitrary at some images, in which I think there should be a "default" or "fallback" variable that can be defined.

What. lol. I'll take a look at this tomorrow after some rest. Feel free to elaborate if you want.

...

So like a prescan to lookahead to pick a strategy on how to gather and process colors? That might be effective... I know right now for the cam16 implementation I need different strategies on background and color filtering because light theme just... you need a bigger difference in lightness to be able to distinguish contrast because the light background blinds light colors.

Also, it could work for the contrast ratio of sorts to determine the best threshold to start with, then explore for the best threshold. But even then what is the best threshold? More than 6 colors but less than 16? I would prefer the latter as it allows for user adjustment of contrast with sampling.

For cam16, I imagine...

  1. get the lightnest, colorness color
  2. get the darkest, least colorness color
  3. calculate improved_delta_e (the same way we do col_diff)
  4. divide by the amount of colors we're searching for(?) (6-16) and start searching for best threshold

I want to say this would cover a variety, if not all, image styles, from dark low contrast to bright pastel ones, or high contrast. It might struggle in cases where there's just a slither of high contrast in an image.

But... would implementing this be worth it? It makes sense but can't put two-and-two together to envision the result. That's for tomorrow me.

edit - clarification
edit2 - thought on that final part

@explosion-mental wrote in https://codeberg.org/explosion-mental/wallust/pulls/188#issuecomment-8312741: > I did have a bit of issues about the color1 difference with color0 or the background. However, it wasn't that constant, like 30-40%ish of the time. Between two colors, when luminance is equal but chroma differs, it can result in a salience value big enough to bypass the `filter_cols`. This is partially because salience was calculated as a weight sum, so chroma acts independently from luminance, but in actuality, chroma depends on luminance. So instead of `l + c + h` it actually should have been more like `l * c * h` or something like that. I experimented with the latter and it was much better for the Lch implementation, but I don't think I ever pushed it. I since moved to Cam16. It should not have this problem because the chroma dependence on luminance is decoupled into colorfulness. In other words, colorfulness is meant to act completely independent from lightness (luminance). Here is a color picker for demonstration purposes. https://apps.colorjs.io/picker/cam16-jmh It's kind of crazy how it's decoupled. It very easy allows you to pick colors that straight up cannot be displayed, but the deltas are accurate and decoupled. So in this case, a weight sum, or in this case, the sqrt of sums squared, works rather elegantly. But... the issue of equal lightness but differing colorfulness still goes through (e.g. `l=5, m=5` vs `l=5, m=20`, m is colorfulness). This has rather poor contrast, which is to suggest that lightness still dominates colorfulness in salience (or at least the range for colorfulness is very big, it's actually infinite). For now I just did a hard constraint that there must be a delta lightness of some value between the `background` and `col1`. This could probably be fixed by adjusting the weights, which I have done but haven't actually tested without that hard constraint. Could be double patched right now. Regardless, that issue should be no more... hopefully. If you want colors closer to the background, as in less contrast, you may need to lower thresholding to not join colors too much to allow the lower salient ones to come through, but not so low that they get clipped. > Now that you mention about contrast, I must ask, given the thoughts process you have on developing these new modules, you think the contrast process should be in the internals (histogram/colorspaces) or is it fine as an external process? I think the later was the simpler option, but now that the restructuring thing is in the [mental] works, would help your comment on it. That's interesting. I always had in the back of my head that it was odd that I didn't need the check contrast flag because it just worked for my image sets. To me, if the contrast wasn't good AND we are at a pure black background, it means the colors the filter was too narrow, colors were too aggressively merged, or straight up the provided image is not good. I haven't tried how robust the implementation is, but I'd assume given a very dark image with very dark colors and very few of them, it would struggle to capture a palette. So it's possible the external contrast check is a patch for oddities in how the background is created or/and how the colors are picked/merged. It could probably be fixed internally with some adjustments to DARKEST and/or LIGHTEST or constraints against an early generated background color. > I think this could be improved also in the histogram phase, as it should generate a background and foreground colors in correlation to the image, but not precise. I think it can be generated there, maybe a color pixel from the image is just perfect, or maybe none satisfies the contrast, so we require it to tweak it. I'm a bit confused on what you're brainstorming... but, I know in #189 it was odd to pass colors whose counts are in the single digits. There's times where I got count 1 from the histogram and it passed to the palette section. If we implement #189, we could have a fallback where the constraint for clipping low counts is removed. I also tried my best not to alter the colors themselves (though viewing Parameters does things), so to me this is the complete last resort if we've gone through all the thresholds and we still can't get good contrast. If we get to that point, it means the image has poor contrast to begin with. There's no way to use the colors as is and have good contrast, so we HAVE to alter them. I think I got that from the second half of the quoted statement above. > This correlates to an idea I had, a variable in the templates that determines whether the image is "dark or light", as in, does it leans to darker colors or more pastel lighter ones? This is kinda arbitrary at some images, in which I think there should be a "default" or "fallback" variable that can be defined. What. lol. I'll take a look at this tomorrow after some rest. Feel free to elaborate if you want. ... So like a prescan to lookahead to pick a strategy on how to gather and process colors? That might be effective... I know right now for the cam16 implementation I need different strategies on background and color filtering because light theme just... you need a bigger difference in lightness to be able to distinguish contrast because the light background blinds light colors. Also, it could work for the contrast ratio of sorts to determine the best threshold to start with, then explore for the best threshold. But even then what is the best threshold? More than 6 colors but less than 16? I would prefer the latter as it allows for user adjustment of contrast with sampling. For cam16, I imagine... 1. get the lightnest, colorness color 2. get the darkest, least colorness color 3. calculate `improved_delta_e` (the same way we do `col_diff`) 4. divide by the amount of colors we're searching for(?) (6-16) and start searching for best threshold I want to say this would cover a variety, if not all, image styles, from dark low contrast to bright pastel ones, or high contrast. It might struggle in cases where there's just a slither of high contrast in an image. But... would implementing this be worth it? It makes sense but can't put two-and-two together to envision the result. That's for tomorrow me. edit - clarification edit2 - thought on that final part
Author
Contributor
Copy link

The last two commits better rewards saturated and complementary colors during the sorting process, resulting in much more vivid colors being sorted. It also prevents colors from being washed out due to brightness dominating the salience score. The calculated score also supposedly better aligns with ImprovedDeltaE scaling, so numbers should roughly mean the same thing. Also enforced col0 to be threshold 1 from bg, which is the roughly (according to ImprovedDeltaE) the just-noticable difference threshold.

I also made some adjustments to the viewing conditions... still not too sure what would be optimal here...

Examples below use saliencedark. In other words, it samples high (the most salient colors).

Old weighting and salience score scaling
20251123_22h49m43s_grim
New
20251123_22h49m48s_grim


Old weighting and salience score scaling
20251123_23h02m16s_grim
New
20251123_23h02m25s_grim


Old weighting and salience score scaling
20251123_23h03m17s_grim
New
20251123_23h03m21s_grim

edit - image annotation, viewing conditions addition

addition - might be a bit too strongly weighted. I haven't been able to test for a while and I'm kind of just pushing a test from a few days ago that I found worked well at that time. Didn't completely validate it though.

The last two commits better rewards saturated and complementary colors during the sorting process, resulting in much more vivid colors being sorted. It also prevents colors from being washed out due to brightness dominating the salience score. The calculated score also supposedly better aligns with ImprovedDeltaE scaling, so numbers should roughly mean the same thing. Also enforced col0 to be threshold 1 from bg, which is the roughly (according to ImprovedDeltaE) the just-noticable difference threshold. I also made some adjustments to the viewing conditions... still not too sure what would be optimal here... Examples below use `saliencedark`. In other words, it samples high (the most salient colors). Old weighting and salience score scaling ![20251123_22h49m43s_grim](/attachments/37de05a3-57f3-4b04-80f5-4b8b57d9db96) New ![20251123_22h49m48s_grim](/attachments/0dec3c2c-4b4a-4b77-9240-7c935d831b5e) --- Old weighting and salience score scaling ![20251123_23h02m16s_grim](/attachments/4a09dc1b-5816-45b3-96a6-9b4b2ed42a96) New ![20251123_23h02m25s_grim](/attachments/a9c079e4-bd82-4f0a-8cab-682bb8901794) --- Old weighting and salience score scaling ![20251123_23h03m17s_grim](/attachments/c70a720e-6835-43ce-925a-a7e2b857ad40) New ![20251123_23h03m21s_grim](/attachments/1fb9aa64-c0bc-4f89-ab01-5709a4537faa) edit - image annotation, viewing conditions addition addition - might be a bit too strongly weighted. I haven't been able to test for a while and I'm kind of just pushing a test from a few days ago that I found worked well at that time. Didn't completely validate it though.

That's interesting. I always had in the back of my head that it was odd that I didn't need the check contrast flag because it just worked for my image sets. To me, if the contrast wasn't good AND we are at a pure black background, it means the colors the filter was too narrow, colors were too aggressively merged, or straight up the provided image is not good. I haven't tried how robust the implementation is, but I'd assume given a very dark image with very dark colors and very few of them, it would struggle to capture a palette.

So it's possible the external contrast check is a patch for oddities in how the background is created or/and how the colors are picked/merged. It could probably be fixed internally with some adjustments to DARKEST and/or LIGHTEST or constraints against an early generated background color.

Makes sense, I really wanted to strictly use the colors from the image, maintaining the highest fidelity as possible. However, contrast was really an issue. Maybe, the search for color0 and color1 can be done differently, with a special method, e.g. using salience with contrast as the goal. I didn't really find a way around the histogram plateau caused by forcing versatility.

I think this can be one goal of the new histogram, ensure contrast. Now, since that is more or less subjective (and dependent on the font size, as the WCAG standard indicates), a "threshold" can be used to measure this. (I'm thinking, instead of an int, a string that has some default or predefined thresholds e.g. threshold = "tv" sets it better for bigger fonts, but allows it to specify directly as well threshold = "10").
Edit2: I'll elaborate later, but mind that threshold is different than the variable that is used when comparing two colors and determining a good contrast. However, it has relation with colordiference. (I'm leaving this here since I'm brainstorming)

The threshold situation alone I think can be the second step toward the histogram changes, given the different takes based on the colorspace.

I think this PR is ready. The tweaks you are pushing are simple enough to be small PRs after some "mid" term testing, and I think we can address the histogram thing for "once and for all"

Edit: I'm pushing this at the weekend

> That's interesting. I always had in the back of my head that it was odd that I didn't need the check contrast flag because it just worked for my image sets. To me, if the contrast wasn't good AND we are at a pure black background, it means the colors the filter was too narrow, colors were too aggressively merged, or straight up the provided image is not good. I haven't tried how robust the implementation is, but I'd assume given a very dark image with very dark colors and very few of them, it would struggle to capture a palette. > So it's possible the external contrast check is a patch for oddities in how the background is created or/and how the colors are picked/merged. It could probably be fixed internally with some adjustments to DARKEST and/or LIGHTEST or constraints against an early generated background color. Makes sense, I really wanted to strictly use the colors from the image, maintaining the highest fidelity as possible. However, contrast was really an issue. Maybe, the search for `color0` and `color1` can be done differently, with a special method, e.g. using salience with contrast as the goal. I didn't really find a way around the histogram _plateau_ caused by forcing versatility. I think this can be one goal of the new histogram, ensure contrast. Now, since that is more or less subjective (and dependent on the font size, as the WCAG standard indicates), a "threshold" can be used to measure this. (I'm thinking, instead of an int, a string that has some default or predefined thresholds e.g. `threshold = "tv"` sets it better for bigger fonts, but allows it to specify directly as well `threshold = "10"`). Edit2: I'll elaborate later, but mind that threshold is different than the variable that is used when comparing two colors and determining a good contrast. However, it has relation with colordiference. (I'm leaving this here since I'm brainstorming) The threshold situation alone I think can be the second step toward the histogram changes, given the different takes based on the colorspace. I think this PR is ready. The tweaks you are pushing are simple enough to be small PRs after some "mid" term testing, and I think we can address the histogram thing for "once and for all" Edit: I'm pushing this at the weekend
Author
Contributor
Copy link

Yeah it's done. Any other adjustment now will (for reals this time) just be an adjustment to contrast for cases where color1 does not contrast well for against bg. All it would be is a const adjustment.

Maybe, the search for color0 and color1 can be done differently, with a special method, e.g. using salience with contrast as the goal.

For colorspace and palette generation for Salience, IF there are enough colors, color0 uses the extra colors provided, which is actually a different color from color1 with a salience difference of 2*threshold (if I brained that correctly). The reason why 2*threshold is because the way the colors are merged. Color A must be 2*threshold away from Color B, because everything threshold away from a color gets merged into itself.

When there is a minimal amount of colors (6 colors), then color0 derives its color from color1. It still goes through the bg vs col1 adjustments, but it's with a pre-defined constant, not by threshold, so there may be some inconsistencies in contrast there now that I think about it. The constants are there to only define an absolute minimum contrast for legibility against bg and col1, which are usually lower than threshold (const as 7.5 as opposed to a somewhat usual threshold 12).

I think this can be one goal of the new histogram, ensure contrast. Now, since that is more or less subjective (and dependent on the font size, as the WCAG standard indicates), a "threshold" can be used to measure this. (I'm thinking, instead of an int, a string that has some default or predefined thresholds e.g. threshold = "tv" sets it better for bigger fonts, but allows it to specify directly as well threshold = "10").

I did not consider font size as a factor in legibility. I don't know if that's something wallust should consider when generating palettes, as palettes aren't always going to be used for fonts. But I would say fonts is the still THE most important thing. What I'm trying to get at is no matter what the font size is, if it's too small, color won't help too much in reading it. You might be able to jump your eyes to a salient color, but if you have trouble reading it because it's too small, then it's just too small.

But... font size corresponds to density. With a smaller font size, there is much more on the screen, and thus, more colors. If the samples colors are all extremely salient (e.g. all bright neon colors), it's going to be very straining to read compared to content of lower density. If the user wants small font with high density, then they can solve the eye strain with an adjustment to the sampling mode. It also heavily depends on how the user assigns the colors to the text elements.

But that said, sampling modes depend on how the colors were merged together. With a lower threshold, there will be more colors and thus less difference. With a high threshold, with 6 generated colors it can be balanced if the image is balanced. But if the image is entirely bright neon then those 6 colors will reflect that. A lower threshold will lessen the salience difference, allowing sampling to finely choose colors in between.

With all that said... the new histogram needs to innately generate more colors such that when we sample via salience, we have a finer grain to sample colors from. This can mostly be solved by lowering default starting threshold.

edit addition - An idea I had is to allow the user to define a salience array such that elements are N salience away from the generated background. So like [10, 20, 35, 45, 60, 70] would mean that color1 is 10 salience away from bg, color2 is 20 salience away from bg... etc. This is... probably overkill, but if users want color6 to be extremely salience and very clearly alarming, they can do that. Maybe instead of the number mapping to salience, maybe it being a percentage of the generated colors from colorspaces. As in, a value of 50% would sample the middle color, and 90 would sample the closest color 90% towards the end.


I'm not too sure what the next steps here would be, but I'll be here now that I've dipped myself quite deep into the codebase. I know you also mentioned potentially adjusting how the pipeline works, like instead of:
backend -> colorspaces -> palette

it's something more intuitive like:
image processing -> how colors are gathered and organized -> how to sample those colors

Just let me know what you plan to do next. I can give feedback and provide sanity checks for this whole salience/contrast thing and maybe help out where I can.

Yeah it's done. Any other adjustment now will (for reals this time) just be an adjustment to contrast for cases where `color1` does not contrast well for against `bg`. All it would be is a const adjustment. > Maybe, the search for `color0` and `color1` can be done differently, with a special method, e.g. using salience with contrast as the goal. For colorspace and palette generation for Salience, IF there are enough colors, `color0` uses the extra colors provided, which is actually a different color from `color1` with a salience difference of `2*threshold` (if I brained that correctly). The reason why `2*threshold` is because the way the colors are merged. Color A must be `2*threshold` away from Color B, because everything `threshold` away from a color gets merged into itself. When there is a minimal amount of colors (6 colors), then `color0` derives its color from `color1`. It still goes through the `bg` vs `col1` adjustments, but it's with a pre-defined constant, not by `threshold`, so there may be some inconsistencies in contrast there now that I think about it. The constants are there to only define an absolute minimum contrast for legibility against `bg` and `col1`, which are usually lower than `threshold` (const as 7.5 as opposed to a somewhat usual threshold 12). > I think this can be one goal of the new histogram, ensure contrast. Now, since that is more or less subjective (and dependent on the font size, as the WCAG standard indicates), a "threshold" can be used to measure this. (I'm thinking, instead of an int, a string that has some default or predefined thresholds e.g. threshold = "tv" sets it better for bigger fonts, but allows it to specify directly as well threshold = "10"). I did not consider font size as a factor in legibility. I don't know if that's something wallust should consider when generating palettes, as palettes aren't always going to be used for fonts. But I would say fonts is the still THE most important thing. What I'm trying to get at is no matter what the font size is, if it's too small, color won't help too much in reading it. You might be able to jump your eyes to a salient color, but if you have trouble reading it because it's too small, then it's just too small. But... font size corresponds to density. With a smaller font size, there is much more on the screen, and thus, more colors. If the samples colors are all extremely salient (e.g. all bright neon colors), it's going to be very straining to read compared to content of lower density. If the user wants small font with high density, then they can solve the eye strain with an adjustment to the sampling mode. It also heavily depends on how the user assigns the colors to the text elements. But that said, sampling modes depend on how the colors were merged together. With a lower threshold, there will be more colors and thus less difference. With a high threshold, with 6 generated colors it can be balanced if the image is balanced. But if the image is entirely bright neon then those 6 colors will reflect that. A lower threshold will lessen the salience difference, allowing sampling to finely choose colors in between. With all that said... the new histogram needs to innately generate more colors such that when we sample via salience, we have a finer grain to sample colors from. This can mostly be solved by lowering default starting threshold. edit addition - An idea I had is to allow the user to define a salience array such that elements are `N` salience away from the generated background. So like `[10, 20, 35, 45, 60, 70]` would mean that `color1` is `10` salience away from `bg`, `color2` is `20` salience away from `bg`... etc. This is... probably overkill, but if users want `color6` to be extremely salience and very clearly alarming, they can do that. Maybe instead of the number mapping to salience, maybe it being a percentage of the generated colors from colorspaces. As in, a value of 50% would sample the middle color, and 90 would sample the closest color 90% towards the end. --- I'm not too sure what the next steps here would be, but I'll be here now that I've dipped myself quite deep into the codebase. I know you also mentioned potentially adjusting how the pipeline works, like instead of: ```backend -> colorspaces -> palette``` it's something more intuitive like: ```image processing -> how colors are gathered and organized -> how to sample those colors``` Just let me know what you plan to do next. I can give feedback and provide sanity checks for this whole salience/contrast thing and maybe help out where I can.
Author
Contributor
Copy link

Oh, and there really needs to be a way to print colors to debug them that can be used across the project. I used it heavily to check if the salience score sorted correctly from colorspaces and palette. The amount of times I had to copy the print statements, only to delete them before committing, was... a lot. I made use of that owo colors crate to sanity check myself when sorting colors.

I used something like this.

pubfn print_visual(cols: &[Spec]){useowo_colors::OwoColorize;letcols: Vec<Myrgb>=cols.iter().map(|&c|c.into()).collect();println!();letchunk_size=10;for(chunk_index,chunk)incols.chunks(chunk_size).enumerate(){letstart=chunk_index*chunk_size;// Print indices
foriin0..chunk.len(){ifi==0{print!("{:^3} ",(chunk_index)%10);}else{print!("{:^3} ",(start+i)%10);}}println!();// Print values
forvalinchunk{print!("{} "," ".on_color(val.owo_col()));}println!();println!();}}

Just mapped the colors to a flat Spec vector and passed the slice to debug.

Oh, and there really needs to be a way to print colors to debug them that can be used across the project. I used it heavily to check if the salience score sorted correctly from colorspaces and palette. The amount of times I had to copy the print statements, only to delete them before committing, was... a lot. I made use of that owo colors crate to sanity check myself when sorting colors. I used something like this. ```rust pub fn print_visual(cols: &[Spec]) { use owo_colors::OwoColorize; let cols: Vec<Myrgb> = cols.iter().map(|&c| c.into()).collect(); println!(); let chunk_size = 10; for (chunk_index, chunk) in cols.chunks(chunk_size).enumerate() { let start = chunk_index * chunk_size; // Print indices for i in 0..chunk.len() { if i == 0 { print!("{:^3} ", (chunk_index) % 10); } else { print!("{:^3} ", (start + i) % 10); } } println!(); // Print values for val in chunk { print!("{} ", " ".on_color(val.owo_col())); } println!(); println!(); } } ``` Just mapped the colors to a flat `Spec` vector and passed the slice to debug.

I did not consider font size as a factor in legibility. I don't know if that's something wallust should consider when generating palettes, as palettes aren't always going to be used for fonts. But I would say fonts is the still THE most important thing. What I'm trying to get at is no matter what the font size is, if it's too small, color won't help too much in reading it. You might be able to jump your eyes to a salient color, but if you have trouble reading it because it's too small, then it's just too small.

Mostly referring to this

1.4.3 Contrast (Minimum): The visual presentation of text and images of text has a contrast ratio of at least 4.5:1, except for the following: (Level AA)
 Large Text: Large-scale text and images of large-scale text have a contrast ratio of at least 3:1;
 Incidental: Text or images of text that are part of an inactive user interface component, that are pure decoration, that are not visible to anyone, or that are part of a picture that contains significant other visual content, have no contrast requirement.
 Logotypes: Text that is part of a logo or brand name has no minimum contrast requirement.

edit addition - An idea I had is to allow the user to define a salience array such that elements are N salience away from the generated background. So like [10, 20, 35, 45, 60, 70] would mean that color1 is 10 salience away from bg, color2 is 20 salience away from bg... etc. This is... probably overkill, but if users want color6 to be extremely salience and very clearly alarming, they can do that. Maybe instead of the number mapping to salience, maybe it being a percentage of the generated colors from colorspaces. As in, a value of 50% would sample the middle color, and 90 would sample the closest color 90% towards the end.

This is interesting, indeed. This can be optional, but configurable for more flexibility. Not sure how it will affect the color gathering process overall.

Just let me know what you plan to do next. I can give feedback and provide sanity checks for this whole salience/contrast thing and maybe help out where I can.

Currently, I'm a bit busy to start coding right away, so I'm getting my head around the importance and relevance of color spaces. In my head tho, I'm

  1. restructuring and rewriting everything into LCH, for standard histo, and moving the UCAM impl for the salience one.
  2. After that, modify colorspace, so it only serves to identify the "type" of image. Monochrome or "normal" usual image 1 .
  3. The response to that would be, use2 the histogram that "generates" colors depending on the missing colors (less than 6).
  4. Think how ANSI would be implemented, probably as a "modifier" that interacts inside the new histogram module.
  5. After all the above, then there should be a logic for palette to place the colors. Given that the colorspaces generics restrictions would probably be eliminated, pointless convertions to rgb can be omitted and complicated sorting can be used (e.g. the tweaks you made on salience).

Then probably change multithreading x.x..

On number 3: Just reuse the fallback generator interpolation method, with LCH as the rest of the histo to complete. A different method could be done, probably, interpolation for only 2 or 3 colors done, after that, use other generation methods. This requires deduplication first. to remove the similar colors.

Now, here I'm just going to throw how ANSI does it, how can it be upgraded, but I'm not sure if it helps the dedup methodology. Ansi looks up for a range of hues, with a more or less vivid chroma, whatever is inside the range of "Red" is considered to be redish (even tho it may feel like pink or orange or ..). This can be upgraded by dynamics functions that, even tho it follows the range of hue, adds context from the image (overall colors, or rather, pixels) to the equation, justifying higher/lower chroma values and probable edge cases on hue (maybe there is one redish color that is suficient or is very very close to the range, in which, if the context is favorable, can be used).


  1. There are images (referring to vast wallpapers) that are black and white, AND have very few colors. ↩︎

  2. have an alternative to salience, since buckets are 'useless' here ↩︎

> I did not consider font size as a factor in legibility. I don't know if that's something wallust should consider when generating palettes, as palettes aren't always going to be used for fonts. But I would say fonts is the still THE most important thing. What I'm trying to get at is no matter what the font size is, if it's too small, color won't help too much in reading it. You might be able to jump your eyes to a salient color, but if you have trouble reading it because it's too small, then it's just too small. Mostly referring to [this](https://www.w3.org/TR/2008/REC-WCAG20-20081211/#content-structure-separation) ``` 1.4.3 Contrast (Minimum): The visual presentation of text and images of text has a contrast ratio of at least 4.5:1, except for the following: (Level AA) Large Text: Large-scale text and images of large-scale text have a contrast ratio of at least 3:1; Incidental: Text or images of text that are part of an inactive user interface component, that are pure decoration, that are not visible to anyone, or that are part of a picture that contains significant other visual content, have no contrast requirement. Logotypes: Text that is part of a logo or brand name has no minimum contrast requirement. ``` > edit addition - An idea I had is to allow the user to define a salience array such that elements are N salience away from the generated background. So like [10, 20, 35, 45, 60, 70] would mean that color1 is 10 salience away from bg, color2 is 20 salience away from bg... etc. This is... probably overkill, but if users want color6 to be extremely salience and very clearly alarming, they can do that. Maybe instead of the number mapping to salience, maybe it being a percentage of the generated colors from colorspaces. As in, a value of 50% would sample the middle color, and 90 would sample the closest color 90% towards the end. This is interesting, indeed. This can be optional, but configurable for more flexibility. Not sure how it will affect the color gathering process overall. > Just let me know what you plan to do next. I can give feedback and provide sanity checks for this whole salience/contrast thing and maybe help out where I can. Currently, I'm a bit busy to start coding right away, so I'm getting my head around the importance and relevance of color spaces. In my head tho, I'm 1. restructuring and rewriting everything into LCH, for standard histo, and moving the UCAM impl for the salience one. 2. After that, modify colorspace, so it only serves to identify the "type" of image. Monochrome or "normal" usual image [^1]. 3. The response to that would be, use[^2] the histogram that "generates" colors depending on the missing colors (less than 6). 4. Think how ANSI would be implemented, probably as a "modifier" that interacts inside the new `histogram` module. 5. After all the above, then there should be a logic for palette to place the colors. Given that the colorspaces generics restrictions would probably be eliminated, pointless convertions to rgb can be omitted and complicated sorting can be used (e.g. the tweaks you made on salience). Then probably change multithreading x.x.. On number 3: Just reuse the `fallback generator` interpolation method, with LCH as the rest of the histo to complete. A different method could be done, probably, interpolation for only 2 or 3 colors done, after that, use other generation methods. This requires `dedup`lication first. to remove the similar colors. Now, here I'm just going to throw how ANSI does it, how can it be upgraded, but I'm not sure if it helps the dedup methodology. Ansi looks up for a range of hues, with a more or less vivid chroma, whatever is inside the range of "Red" is considered to be redish (even tho it may _feel_ like pink or orange or ..). This can be upgraded by dynamics functions that, even tho it follows the range of hue, adds context from the image (overall colors, or rather, pixels) to the equation, justifying higher/lower chroma values and probable edge cases on hue (maybe there is one redish color that is suficient or is very very close to the range, in which, if the context is favorable, can be used). [^1]: There are images (referring to vast wallpapers) that are black and white, AND have very few colors. [^2]: have an alternative to salience, since buckets are 'useless' here
Author
Contributor
Copy link

@explosion-mental wrote in #188 (comment):

This is interesting, indeed. This can be optional, but configurable for more flexibility. Not sure how it will affect the color gathering process overall.

Not the color gathering, but the sampling step. So if there are 100 gathered colors, then sorted by descending salience, an array of [1, 10, 40, 60, 90, 99] would sample exactly those colors at the indices. Obviously when colors merge, there may be only only 24 colors or something, but the sampler will scale the logic down from 100 to 24.


That ANSI one seems like a doozy... I'm curious how many people actually use it though.

I agree with the idea that "colorspaces" should more-so be "what is our best strategy for gathering colors". Once I understood the colorspaces module, I found its existence strange. Yes the user can choose between Lab and Lch, but like, me as a normal user who has little clue about colorspaces will have no idea what it all means and how it affects the color gathering process. And even now, I still find it a bit strange that it's a user choice, it doesn't seem to have any meaningful effect on the resulting palette. Yes the palette is not the same, but you can't really put into simple words how they are different.

Like for LchDark vs Salience, we can say that LchDark currently sorts by brightness while Salience sorts by salience, which accounts for brightness, saturation, and complementary color against background. But for Lab vs Lch? Uh... it's different because it uses a which has opposing color channels for the eye and lch is the polar transformation which... blah blah something... difficult to make sense of. And at the end of the day they both sort by brightness.

iirc the DeltaE for Lab and Lch are the same? So there isn't even much of a difference with how colors are merged.

So the pipeline is sounding like...

backend to transform and prepare image -> analyze image and strategize best histo gather method -> merge and dedup according to some standardized method -> sorting -> maybe truncating/clipping -> sampling

And uh, there's also the thought of whether to use non-improved DeltaE or ImprovedDeltaE. Do you recall that discussion and how the latter might be difficult to use?

@explosion-mental wrote in https://codeberg.org/explosion-mental/wallust/pulls/188#issuecomment-8518284: > This is interesting, indeed. This can be optional, but configurable for more flexibility. Not sure how it will affect the color gathering process overall. Not the color gathering, but the sampling step. So if there are 100 gathered colors, then sorted by descending salience, an array of `[1, 10, 40, 60, 90, 99]` would sample exactly those colors at the indices. Obviously when colors merge, there may be only only 24 colors or something, but the sampler will scale the logic down from 100 to 24. --- That ANSI one seems like a doozy... I'm curious how many people actually use it though. I agree with the idea that "colorspaces" should more-so be "what is our best strategy for gathering colors". Once I understood the `colorspaces` module, I found its existence strange. Yes the user can choose between Lab and Lch, but like, me as a normal user who has little clue about colorspaces will have no idea what it all means and how it affects the color gathering process. And even now, I still find it a bit strange that it's a user choice, it doesn't seem to have any meaningful effect on the resulting palette. Yes the palette is not the same, but you can't really put into simple words how they are different. Like for `LchDark` vs `Salience`, we can say that `LchDark` currently sorts by brightness while `Salience` sorts by salience, which accounts for brightness, saturation, and complementary color against background. But for `Lab` vs `Lch`? Uh... it's different because it uses `a` which has opposing color channels for the eye and `lch` is the polar transformation which... blah blah something... difficult to make sense of. And at the end of the day they both sort by brightness. iirc the DeltaE for `Lab` and `Lch` are the same? So there isn't even much of a difference with how colors are merged. So the pipeline is sounding like... ``` backend to transform and prepare image -> analyze image and strategize best histo gather method -> merge and dedup according to some standardized method -> sorting -> maybe truncating/clipping -> sampling ``` --- And uh, there's also the thought of whether to use non-improved DeltaE or ImprovedDeltaE. Do you recall that discussion and how the latter might be difficult to use?
Author
Contributor
Copy link

I don't think I ever mentioned it, but the primary reason why I wanted to do this was in response to this article.

https://tonsky.me/blog/syntax-highlighting/
https://www.reddit.com/r/neovim/comments/1o8vmeb/i_am_sorry_but_everyone_is_getting_syntax/
https://www.youtube.com/watch?v=WQdSE1AxiJU

I thought the article had good ideas, but the execution was absolutely atrocious. It was so bad I was I was driven to fix it myself, hence why Salience is now a thing in Wallust. Sometime in the future once I actually set up neovim to better utilize the salience-sorted colors, I will probably post something on reddit in response to that article to help promote Wallust and salience-sorting, just a heads-up.

I don't think I ever mentioned it, but the primary reason why I wanted to do this was in response to this article. https://tonsky.me/blog/syntax-highlighting/ https://www.reddit.com/r/neovim/comments/1o8vmeb/i_am_sorry_but_everyone_is_getting_syntax/ https://www.youtube.com/watch?v=WQdSE1AxiJU I thought the article had good ideas, but the execution was absolutely atrocious. It was so bad I was I was driven to fix it myself, hence why Salience is now a thing in Wallust. Sometime in the future once I actually set up neovim to better utilize the salience-sorted colors, I will probably post something on reddit in response to that article to help promote Wallust and salience-sorting, just a heads-up.

I'm working on it. Let me ping you at a new pr I'm making.

I'm working on it. Let me ping you at a new pr I'm making.
Sign in to join this conversation.
No reviewers
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
explosion-mental/wallust!188
Reference in a new issue
explosion-mental/wallust
No description provided.
Delete branch "usaradark/wallust:salience-v5"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?