Skip to content

Conversation

@coolpx
Copy link

@coolpx coolpx commented Nov 23, 2025

This has been a wishlist feature for me for a while, so I decided to add it 😊

This preserves contrast when using a very low color temperature (close to 500K). It's rooted-only since it can't be replicated without SurfaceFlinger.

Tested on my Pixel 8 Pro running Android 16.

Simulated screenshots (you can see the largest difference in the download links, they're nearly invisible on low brightness without this):
enabled
disabled

Introduces a new monochrome setting that applies a high-quality
grayscale filter before applying color temperature. This allows users
with very low color temperatures (500K) to still distinguish blue
objects as different shades of gray before the warm tint is applied.

Implementation:
- Requires root mode (uses SurfaceFlinger color matrix transformation)
- Applies luminosity-based grayscale (0.299R + 0.587G + 0.114B)
- Automatically hidden when root mode is disabled
- Added UI checkbox with descriptive summary and root requirement notice
- Backward compatible (defaults to false)

Changes:
- Added monochrome boolean field to Profile data class with JSON support
- Implemented color matrix transformation in SurfaceFlinger
- Added monochrome preference to Config with profile activation
- Added FilterFragment logic to show/hide based on root mode availability
- Added UI strings and preference resources

The feature preserves contrast with extreme color temperatures while
maintaining the benefits of blue light reduction.
The grgit 4.1.0 dependency was not available in Maven repositories,
causing build failures. Updated to 4.1.1 which is available and
compatible with the existing Gradle and Kotlin versions.

Also added gradlePluginPortal() repository to buildscript to ensure
proper dependency resolution.
Copy link
Member

@smichel17 smichel17 left a comment

Choose a reason for hiding this comment

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

Thanks, this looks really nice! Just a few small suggestions.

}

var monochrome by BooleanPreference(R.string.pref_key_monochrome, false) {
activeProfile.run { if (it != monochrome) activateProfile(copy(monochrome = it)) }
Copy link
Member

Choose a reason for hiding this comment

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

I'm sure this works, since it matches the other ones, but ugh, I was so hyperfixated on making code concise (specifically as measured by lines of code) when I wrote this.

Today I'd write it more like this (assuming I have correctly inferred it; I did this in GitHub webui):

Suggested change
activeProfile.run { if (it != monochrome) activateProfile(copy(monochrome = it)) }
if (it != monochrome) {
activateProfile(activeProfile.copy(monochrome = it))
}

No need to change this, unless you want to change the others too, which you are more than welcome to.

Comment on lines +33 to 34
val monochrome: Boolean = false)
: Event, Comparable<Profile> {
Copy link
Member

Choose a reason for hiding this comment

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

Does Kotlin support trailing commas now?

Suggested change
val monochrome: Boolean = false)
: Event, Comparable<Profile> {
val monochrome: Boolean = false,
) : Event, Comparable<Profile> {

Shell.su(call).exec()
val profile = activeProfile

if (profile.monochrome) {
Copy link
Member

Choose a reason for hiding this comment

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

There's a lot of duplication here, relative to the unique code. Here's how I'd suggest handling it:

  • Move the r, g, and b declarations before the if. Maybe they'll be calculated differently in the future, but for now they're the same, so no need to prematurely split code paths.
  • Extract a common call function which takes the matrix part of the string, builds and logs the command, and does the SU call. Each of the branches would now end with this.

pref.summary = if (Config.useRoot) {
getString(R.string.pref_summary_monochrome)
} else {
getString(R.string.pref_summary_monochrome_requires_root)
Copy link
Member

Choose a reason for hiding this comment

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

If we're hiding the pref when root is disabled, then no need for the "requires root" description, right?

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants