From b80741d531a0ca8ab2a2632f3a7182f2c2540094 Mon Sep 17 00:00:00 2001 From: Fushan Wen Date: Fri, 6 Oct 2023 00:39:46 +0800 Subject: [PATCH] plugins: add colorblindness correction effect Colorblindness correction shader with adjustable intensity. Can correct for: - Protanopia (Greatly reduced reds) - Deuteranopia (Greatly reduced greens) - Tritanopia (Greatly reduced blues) FEATURE: 474470 FIXED-IN: 6.0 --- src/plugins/CMakeLists.txt | 1 + .../colorblindnesscorrection/CMakeLists.txt | 36 +++++ .../colorblindnesscorrection.cpp | 136 ++++++++++++++++++ .../colorblindnesscorrection.h | 56 ++++++++ .../colorblindnesscorrection.qrc | 10 ++ .../colorblindnesscorrection_config.cpp | 59 ++++++++ .../colorblindnesscorrection_config.h | 35 +++++ .../colorblindnesscorrection_settings.kcfg | 16 +++ .../colorblindnesscorrection_settings.kcfgc | 9 ++ .../kwin_colorblindnesscorrection_config.json | 6 + ...lorblindnesscorrection_config.json.license | 2 + src/plugins/colorblindnesscorrection/main.cpp | 16 +++ .../colorblindnesscorrection/metadata.json | 10 ++ .../metadata.json.license | 2 + .../shaders/Deutranopia.frag | 40 ++++++ .../shaders/Deutranopia_core.frag | 43 ++++++ .../shaders/Protanopia.frag | 40 ++++++ .../shaders/Protanopia_core.frag | 43 ++++++ .../colorblindnesscorrection/shaders/README | 8 ++ .../shaders/Tritanopia.frag | 43 ++++++ .../shaders/Tritanopia_core.frag | 43 ++++++ .../colorblindnesscorrection/ui/main.qml | 83 +++++++++++ 22 files changed, 737 insertions(+) create mode 100644 src/plugins/colorblindnesscorrection/CMakeLists.txt create mode 100644 src/plugins/colorblindnesscorrection/colorblindnesscorrection.cpp create mode 100644 src/plugins/colorblindnesscorrection/colorblindnesscorrection.h create mode 100644 src/plugins/colorblindnesscorrection/colorblindnesscorrection.qrc create mode 100644 src/plugins/colorblindnesscorrection/colorblindnesscorrection_config.cpp create mode 100644 src/plugins/colorblindnesscorrection/colorblindnesscorrection_config.h create mode 100644 src/plugins/colorblindnesscorrection/colorblindnesscorrection_settings.kcfg create mode 100644 src/plugins/colorblindnesscorrection/colorblindnesscorrection_settings.kcfgc create mode 100644 src/plugins/colorblindnesscorrection/kwin_colorblindnesscorrection_config.json create mode 100644 src/plugins/colorblindnesscorrection/kwin_colorblindnesscorrection_config.json.license create mode 100644 src/plugins/colorblindnesscorrection/main.cpp create mode 100644 src/plugins/colorblindnesscorrection/metadata.json create mode 100644 src/plugins/colorblindnesscorrection/metadata.json.license create mode 100644 src/plugins/colorblindnesscorrection/shaders/Deutranopia.frag create mode 100644 src/plugins/colorblindnesscorrection/shaders/Deutranopia_core.frag create mode 100644 src/plugins/colorblindnesscorrection/shaders/Protanopia.frag create mode 100644 src/plugins/colorblindnesscorrection/shaders/Protanopia_core.frag create mode 100644 src/plugins/colorblindnesscorrection/shaders/README create mode 100644 src/plugins/colorblindnesscorrection/shaders/Tritanopia.frag create mode 100644 src/plugins/colorblindnesscorrection/shaders/Tritanopia_core.frag create mode 100644 src/plugins/colorblindnesscorrection/ui/main.qml diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt index f88585e932..198634df4c 100644 --- a/src/plugins/CMakeLists.txt +++ b/src/plugins/CMakeLists.txt @@ -51,6 +51,7 @@ add_subdirectory(backgroundcontrast) add_subdirectory(blendchanges) add_subdirectory(blur) add_subdirectory(buttonrebinds) +add_subdirectory(colorblindnesscorrection) add_subdirectory(colord-integration) add_subdirectory(colorpicker) add_subdirectory(desktopchangeosd) diff --git a/src/plugins/colorblindnesscorrection/CMakeLists.txt b/src/plugins/colorblindnesscorrection/CMakeLists.txt new file mode 100644 index 0000000000..897b09005b --- /dev/null +++ b/src/plugins/colorblindnesscorrection/CMakeLists.txt @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2023 Fushan Wen +# SPDX-License-Identifier: BSD-3-Clause + +kwin_add_builtin_effect(colorblindnesscorrection + colorblindnesscorrection.cpp + colorblindnesscorrection.qrc + main.cpp +) +kconfig_add_kcfg_files(colorblindnesscorrection colorblindnesscorrection_settings.kcfgc GENERATE_MOC) +target_link_libraries(colorblindnesscorrection PRIVATE + kwineffects + kwinglutils + + KF6::ConfigCore + KF6::ConfigGui +) + +# Config +if (NOT KWIN_BUILD_KCMS) + return() +endif() + +kcmutils_add_qml_kcm(kwin_colorblindnesscorrection_config SOURCES colorblindnesscorrection_config.cpp INSTALL_NAMESPACE "kwin/effects/configs" DISABLE_DESKTOP_FILE_GENERATION) +kcmutils_generate_module_data(kwin_colorblindnesscorrection_config + MODULE_DATA_HEADER colorblindnesscorrection_settingsdata.h + MODULE_DATA_CLASS_NAME ColorBlindnessCorrectionSettingsData + SETTINGS_HEADERS colorblindnesscorrection_settings.h + SETTINGS_CLASSES ColorBlindnessCorrectionSettings +) +kconfig_add_kcfg_files(kwin_colorblindnesscorrection_config colorblindnesscorrection_settings.kcfgc GENERATE_MOC) +target_link_libraries(kwin_colorblindnesscorrection_config + KF6::ConfigCore + KF6::KCMUtils + KF6::KCMUtilsQuick + KWinEffectsInterface +) diff --git a/src/plugins/colorblindnesscorrection/colorblindnesscorrection.cpp b/src/plugins/colorblindnesscorrection/colorblindnesscorrection.cpp new file mode 100644 index 0000000000..c6e01da9e1 --- /dev/null +++ b/src/plugins/colorblindnesscorrection/colorblindnesscorrection.cpp @@ -0,0 +1,136 @@ +/* + SPDX-FileCopyrightText: 2023 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "colorblindnesscorrection.h" + +#include + +#include "libkwineffects/glshader.h" +#include "libkwineffects/kwineffects.h" + +#include "colorblindnesscorrection_settings.h" + +Q_LOGGING_CATEGORY(KWIN_COLORBLINDNESS_CORRECTION, "kwin_effect_colorblindnesscorrection", QtWarningMsg) + +static void ensureResources() +{ + // Must initialize resources manually because the effect is a static lib. + Q_INIT_RESOURCE(colorblindnesscorrection); +} + +namespace KWin +{ + +ColorBlindnessCorrectionEffect::ColorBlindnessCorrectionEffect() + : OffscreenEffect() + , m_mode(static_cast(ColorBlindnessCorrectionSettings().mode())) +{ + loadData(); +} + +ColorBlindnessCorrectionEffect::~ColorBlindnessCorrectionEffect() +{ +} + +bool ColorBlindnessCorrectionEffect::supported() +{ + return effects->isOpenGLCompositing(); +} + +void ColorBlindnessCorrectionEffect::loadData() +{ + ensureResources(); + + QString fragPath; + switch (m_mode) { + case Deuteranopia: + fragPath = QStringLiteral(":/effects/colorblindnesscorrection/shaders/Deutranopia.frag"); + break; + case Tritanopia: + fragPath = QStringLiteral(":/effects/colorblindnesscorrection/shaders/Tritanopia.frag"); + break; + case Protanopia: // Most common, use it as fallback + default: + fragPath = QStringLiteral(":/effects/colorblindnesscorrection/shaders/Protanopia.frag"); + break; + } + + m_shader = ShaderManager::instance()->generateShaderFromFile(ShaderTrait::MapTexture, QString(), fragPath); + + if (!m_shader->isValid()) { + qCCritical(KWIN_COLORBLINDNESS_CORRECTION) << "Failed to load the shader!"; + return; + } + + for (const auto windows = effects->stackingOrder(); EffectWindow * w : windows) { + correctColor(w); + } + effects->addRepaintFull(); + + connect(effects, &EffectsHandler::windowDeleted, this, &ColorBlindnessCorrectionEffect::slotWindowDeleted); + connect(effects, &EffectsHandler::windowAdded, this, &ColorBlindnessCorrectionEffect::correctColor); +} + +void ColorBlindnessCorrectionEffect::correctColor(KWin::EffectWindow *w) +{ + if (m_windows.contains(w)) { + return; + } + + redirect(w); + setShader(w, m_shader.get()); + m_windows.insert(w); +} + +void ColorBlindnessCorrectionEffect::slotWindowDeleted(EffectWindow *w) +{ + if (auto it = m_windows.find(w); it != m_windows.end()) { + m_windows.erase(it); + } +} + +bool ColorBlindnessCorrectionEffect::isActive() const +{ + return !m_windows.empty(); +} + +bool ColorBlindnessCorrectionEffect::provides(Feature f) +{ + return f == Contrast; +} + +void ColorBlindnessCorrectionEffect::reconfigure(ReconfigureFlags flags) +{ + if (flags != Effect::ReconfigureAll) { + return; + } + + auto newMode = static_cast(ColorBlindnessCorrectionSettings().mode()); + if (m_mode == newMode) { + return; + } + + m_mode = newMode; + + disconnect(effects, &EffectsHandler::windowDeleted, this, &ColorBlindnessCorrectionEffect::slotWindowDeleted); + disconnect(effects, &EffectsHandler::windowAdded, this, &ColorBlindnessCorrectionEffect::correctColor); + + for (EffectWindow *w : m_windows) { + unredirect(w); + } + m_windows.clear(); + + loadData(); +} + +int ColorBlindnessCorrectionEffect::requestedEffectChainPosition() const +{ + return 98; +} + +} // namespace + +#include "moc_colorblindnesscorrection.cpp" diff --git a/src/plugins/colorblindnesscorrection/colorblindnesscorrection.h b/src/plugins/colorblindnesscorrection/colorblindnesscorrection.h new file mode 100644 index 0000000000..8c8e2ab466 --- /dev/null +++ b/src/plugins/colorblindnesscorrection/colorblindnesscorrection.h @@ -0,0 +1,56 @@ +/* + SPDX-FileCopyrightText: 2023 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include + +#include "libkwineffects/glshadermanager.h" +#include "libkwineffects/kwinoffscreeneffect.h" + +namespace KWin +{ + +/** + * The color filter supports protanopia, deuteranopia and tritanopia. + */ +class ColorBlindnessCorrectionEffect : public OffscreenEffect +{ + Q_OBJECT + +public: + enum Mode { + Protanopia = 0, // m_windows; + std::unique_ptr m_shader; +}; + +} // namespace diff --git a/src/plugins/colorblindnesscorrection/colorblindnesscorrection.qrc b/src/plugins/colorblindnesscorrection/colorblindnesscorrection.qrc new file mode 100644 index 0000000000..154e35c93b --- /dev/null +++ b/src/plugins/colorblindnesscorrection/colorblindnesscorrection.qrc @@ -0,0 +1,10 @@ + + + shaders/Deutranopia.frag + shaders/Deutranopia_core.frag + shaders/Protanopia.frag + shaders/Protanopia_core.frag + shaders/Tritanopia.frag + shaders/Tritanopia_core.frag + + diff --git a/src/plugins/colorblindnesscorrection/colorblindnesscorrection_config.cpp b/src/plugins/colorblindnesscorrection/colorblindnesscorrection_config.cpp new file mode 100644 index 0000000000..0126e8b983 --- /dev/null +++ b/src/plugins/colorblindnesscorrection/colorblindnesscorrection_config.cpp @@ -0,0 +1,59 @@ +/* + SPDX-FileCopyrightText: 2023 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "colorblindnesscorrection_config.h" + +#include +#include +#include + +#include + +#include "colorblindnesscorrection_settings.h" +#include "colorblindnesscorrection_settingsdata.h" +#include "kwineffects_interface.h" + +K_PLUGIN_CLASS_WITH_JSON(KWin::ColorBlindnessCorrectionEffectConfig, "kwin_colorblindnesscorrection_config.json") + +namespace KWin +{ + +ColorBlindnessCorrectionEffectConfig::ColorBlindnessCorrectionEffectConfig(QObject *parent, const KPluginMetaData &metaData) + : KQuickManagedConfigModule(parent, metaData) + , m_data(new ColorBlindnessCorrectionSettingsData(this)) +{ + qmlRegisterUncreatableType("org.kde.plasma.kwin.colorblindnesscorrectioneffect.kcm", + 1, + 0, + "ColorBlindnessCorrectionSettings", + QStringLiteral("Only for enums")); + + setButtons(Apply | Default); +} + +ColorBlindnessCorrectionEffectConfig::~ColorBlindnessCorrectionEffectConfig() +{ +} + +ColorBlindnessCorrectionSettings *ColorBlindnessCorrectionEffectConfig::settings() const +{ + return m_data->settings(); +} + +void ColorBlindnessCorrectionEffectConfig::save() +{ + KQuickManagedConfigModule::save(); + + OrgKdeKwinEffectsInterface interface(QStringLiteral("org.kde.KWin"), + QStringLiteral("/Effects"), + QDBusConnection::sessionBus()); + interface.reconfigureEffect(QStringLiteral("colorblindnesscorrection")); +} + +} // namespace + +#include "colorblindnesscorrection_config.moc" +#include "moc_colorblindnesscorrection_config.cpp" diff --git a/src/plugins/colorblindnesscorrection/colorblindnesscorrection_config.h b/src/plugins/colorblindnesscorrection/colorblindnesscorrection_config.h new file mode 100644 index 0000000000..135590178e --- /dev/null +++ b/src/plugins/colorblindnesscorrection/colorblindnesscorrection_config.h @@ -0,0 +1,35 @@ +/* + SPDX-FileCopyrightText: 2023 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#pragma once + +#include + +class ColorBlindnessCorrectionSettings; +class ColorBlindnessCorrectionSettingsData; + +namespace KWin +{ +class ColorBlindnessCorrectionEffectConfig : public KQuickManagedConfigModule +{ + Q_OBJECT + + Q_PROPERTY(ColorBlindnessCorrectionSettings *settings READ settings CONSTANT) + +public: + explicit ColorBlindnessCorrectionEffectConfig(QObject *parent, const KPluginMetaData &metaData); + ~ColorBlindnessCorrectionEffectConfig() override; + + ColorBlindnessCorrectionSettings *settings() const; + +public Q_SLOTS: + void save() override; + +private: + ColorBlindnessCorrectionSettingsData *m_data; + +}; // namespace +} diff --git a/src/plugins/colorblindnesscorrection/colorblindnesscorrection_settings.kcfg b/src/plugins/colorblindnesscorrection/colorblindnesscorrection_settings.kcfg new file mode 100644 index 0000000000..98e3cd43f6 --- /dev/null +++ b/src/plugins/colorblindnesscorrection/colorblindnesscorrection_settings.kcfg @@ -0,0 +1,16 @@ + + + + + + + 0 + + + diff --git a/src/plugins/colorblindnesscorrection/colorblindnesscorrection_settings.kcfgc b/src/plugins/colorblindnesscorrection/colorblindnesscorrection_settings.kcfgc new file mode 100644 index 0000000000..b084975d1a --- /dev/null +++ b/src/plugins/colorblindnesscorrection/colorblindnesscorrection_settings.kcfgc @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2023 Fushan Wen +# SPDX-License-Identifier: GPL-2.0-or-later + +File=colorblindnesscorrection_settings.kcfg +ClassName=ColorBlindnessCorrectionSettings +Mutators=true +DefaultValueGetters=true +GenerateProperties=true +ParentInConstructor=true diff --git a/src/plugins/colorblindnesscorrection/kwin_colorblindnesscorrection_config.json b/src/plugins/colorblindnesscorrection/kwin_colorblindnesscorrection_config.json new file mode 100644 index 0000000000..6dd348a42a --- /dev/null +++ b/src/plugins/colorblindnesscorrection/kwin_colorblindnesscorrection_config.json @@ -0,0 +1,6 @@ +{ + "KPlugin": { + "License": "GPL-2.0+", + "Name": "Colorblindness Correction" + } +} diff --git a/src/plugins/colorblindnesscorrection/kwin_colorblindnesscorrection_config.json.license b/src/plugins/colorblindnesscorrection/kwin_colorblindnesscorrection_config.json.license new file mode 100644 index 0000000000..66f78fc957 --- /dev/null +++ b/src/plugins/colorblindnesscorrection/kwin_colorblindnesscorrection_config.json.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: None +# SPDX-License-Identifier: CC0-1.0 diff --git a/src/plugins/colorblindnesscorrection/main.cpp b/src/plugins/colorblindnesscorrection/main.cpp new file mode 100644 index 0000000000..216f2a005a --- /dev/null +++ b/src/plugins/colorblindnesscorrection/main.cpp @@ -0,0 +1,16 @@ +/* + SPDX-FileCopyrightText: 2023 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include "colorblindnesscorrection.h" + +namespace KWin +{ + +KWIN_EFFECT_FACTORY_SUPPORTED(ColorBlindnessCorrectionEffect, "metadata.json.stripped", return ColorBlindnessCorrectionEffect::supported();) + +} // namespace KWin + +#include "main.moc" diff --git a/src/plugins/colorblindnesscorrection/metadata.json b/src/plugins/colorblindnesscorrection/metadata.json new file mode 100644 index 0000000000..08d79325f7 --- /dev/null +++ b/src/plugins/colorblindnesscorrection/metadata.json @@ -0,0 +1,10 @@ +{ + "KPlugin": { + "Category": "Accessibility", + "Description": "Enhances color perception for color blindness", + "EnabledByDefault": false, + "License": "GPL-2.0+", + "Name": "Colorblindness Correction" + }, + "X-KDE-ConfigModule": "kwin_colorblindnesscorrection_config" +} diff --git a/src/plugins/colorblindnesscorrection/metadata.json.license b/src/plugins/colorblindnesscorrection/metadata.json.license new file mode 100644 index 0000000000..66f78fc957 --- /dev/null +++ b/src/plugins/colorblindnesscorrection/metadata.json.license @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: None +# SPDX-License-Identifier: CC0-1.0 diff --git a/src/plugins/colorblindnesscorrection/shaders/Deutranopia.frag b/src/plugins/colorblindnesscorrection/shaders/Deutranopia.frag new file mode 100644 index 0000000000..8ec5e257ea --- /dev/null +++ b/src/plugins/colorblindnesscorrection/shaders/Deutranopia.frag @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: None +// SPDX-License-Identifier: CC0-1.0 +uniform sampler2D sampler; +uniform vec4 modulation; +uniform float saturation; +varying vec2 texcoord0; + +void main() +{ + vec4 tex = texture2D(sampler, texcoord0); + + if (saturation != 1.0) { + vec3 desaturated = tex.rgb * vec3( 0.30, 0.59, 0.11 ); + desaturated = vec3(dot( desaturated, tex.rgb )); + tex.rgb = tex.rgb * vec3(saturation) + desaturated * vec3(1.0 - saturation); + } + + float L = (17.8824 * tex.r) + (43.5161 * tex.g) + (4.11935 * tex.b); + float M = (3.45565 * tex.r) + (27.1554 * tex.g) + (3.86714 * tex.b); + float S = (0.0299566 * tex.r) + (0.184309 * tex.g) + (1.46709 * tex.b); + + // Deuteranopia + float l = 1.0 * L + 0.0 * M + 0.0 * S; + float m = 0.494207 * L + 0.0 * M + 1.24827 * S; + float s = 0.0 * L + 0.0 * M + 1.0 * S; + + vec4 error; + error.r = (0.0809444479 * l) + (-0.130504409 * m) + (0.116721066 * s); + error.g = (-0.0102485335 * l) + (0.0540193266 * m) + (-0.113614708 * s); + error.b = (-0.000365296938 * l) + (-0.00412161469 * m) + (0.693511405 * s); + error.a = 1.0; + vec4 diff = tex - error; + vec4 correction; + correction.r = 0.0; + correction.g = (diff.r * 0.7) + (diff.g * 1.0); + correction.b = (diff.r * 0.7) + (diff.b * 1.0); + correction = tex + correction; + + gl_FragColor = correction * modulation; +} diff --git a/src/plugins/colorblindnesscorrection/shaders/Deutranopia_core.frag b/src/plugins/colorblindnesscorrection/shaders/Deutranopia_core.frag new file mode 100644 index 0000000000..71b9c8f6e4 --- /dev/null +++ b/src/plugins/colorblindnesscorrection/shaders/Deutranopia_core.frag @@ -0,0 +1,43 @@ +#version 140 +// SPDX-FileCopyrightText: None +// SPDX-License-Identifier: CC0-1.0 +uniform sampler2D sampler; +uniform vec4 modulation; +uniform float saturation; + +in vec2 texcoord0; +out vec4 fragColor; + +void main() +{ + vec4 tex = texture2D(sampler, texcoord0); + + if (saturation != 1.0) { + vec3 desaturated = tex.rgb * vec3( 0.30, 0.59, 0.11 ); + desaturated = vec3(dot( desaturated, tex.rgb )); + tex.rgb = tex.rgb * vec3(saturation) + desaturated * vec3(1.0 - saturation); + } + + float L = (17.8824 * tex.r) + (43.5161 * tex.g) + (4.11935 * tex.b); + float M = (3.45565 * tex.r) + (27.1554 * tex.g) + (3.86714 * tex.b); + float S = (0.0299566 * tex.r) + (0.184309 * tex.g) + (1.46709 * tex.b); + + // Deuteranopia + float l = 1.0 * L + 0.0 * M + 0.0 * S; + float m = 0.494207 * L + 0.0 * M + 1.24827 * S; + float s = 0.0 * L + 0.0 * M + 1.0 * S; + + vec4 error; + error.r = (0.0809444479 * l) + (-0.130504409 * m) + (0.116721066 * s); + error.g = (-0.0102485335 * l) + (0.0540193266 * m) + (-0.113614708 * s); + error.b = (-0.000365296938 * l) + (-0.00412161469 * m) + (0.693511405 * s); + error.a = 1.0; + vec4 diff = tex - error; + vec4 correction; + correction.r = 0.0; + correction.g = (diff.r * 0.7) + (diff.g * 1.0); + correction.b = (diff.r * 0.7) + (diff.b * 1.0); + correction = tex + correction; + + fragColor = correction * modulation; +} diff --git a/src/plugins/colorblindnesscorrection/shaders/Protanopia.frag b/src/plugins/colorblindnesscorrection/shaders/Protanopia.frag new file mode 100644 index 0000000000..ab34c558bf --- /dev/null +++ b/src/plugins/colorblindnesscorrection/shaders/Protanopia.frag @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: None +// SPDX-License-Identifier: CC0-1.0 +uniform sampler2D sampler; +uniform vec4 modulation; +uniform float saturation; +varying vec2 texcoord0; + +void main() +{ + vec4 tex = texture2D(sampler, texcoord0); + + if (saturation != 1.0) { + vec3 desaturated = tex.rgb * vec3( 0.30, 0.59, 0.11 ); + desaturated = vec3(dot( desaturated, tex.rgb )); + tex.rgb = tex.rgb * vec3(saturation) + desaturated * vec3(1.0 - saturation); + } + + float L = (17.8824 * tex.r) + (43.5161 * tex.g) + (4.11935 * tex.b); + float M = (3.45565 * tex.r) + (27.1554 * tex.g) + (3.86714 * tex.b); + float S = (0.0299566 * tex.r) + (0.184309 * tex.g) + (1.46709 * tex.b); + + // Protanopia + float l = 0.0 * L + 2.02344 * M + -2.52581 * S; + float m = 0.0 * L + 1.0 * M + 0.0 * S; + float s = 0.0 * L + 0.0 * M + 1.0 * S; + + vec4 error; + error.r = (0.0809444479 * l) + (-0.130504409 * m) + (0.116721066 * s); + error.g = (-0.0102485335 * l) + (0.0540193266 * m) + (-0.113614708 * s); + error.b = (-0.000365296938 * l) + (-0.00412161469 * m) + (0.693511405 * s); + error.a = 1.0; + vec4 diff = tex - error; + vec4 correction; + correction.r = 0.0; + correction.g = (diff.r * 0.7) + (diff.g * 1.0); + correction.b = (diff.r * 0.7) + (diff.b * 1.0); + correction = tex + correction; + + gl_FragColor = correction * modulation; +} diff --git a/src/plugins/colorblindnesscorrection/shaders/Protanopia_core.frag b/src/plugins/colorblindnesscorrection/shaders/Protanopia_core.frag new file mode 100644 index 0000000000..c81142d8ba --- /dev/null +++ b/src/plugins/colorblindnesscorrection/shaders/Protanopia_core.frag @@ -0,0 +1,43 @@ +#version 140 +// SPDX-FileCopyrightText: None +// SPDX-License-Identifier: CC0-1.0 +uniform sampler2D sampler; +uniform vec4 modulation; +uniform float saturation; + +in vec2 texcoord0; +out vec4 fragColor; + +void main() +{ + vec4 tex = texture2D(sampler, texcoord0); + + if (saturation != 1.0) { + vec3 desaturated = tex.rgb * vec3( 0.30, 0.59, 0.11 ); + desaturated = vec3(dot( desaturated, tex.rgb )); + tex.rgb = tex.rgb * vec3(saturation) + desaturated * vec3(1.0 - saturation); + } + + float L = (17.8824 * tex.r) + (43.5161 * tex.g) + (4.11935 * tex.b); + float M = (3.45565 * tex.r) + (27.1554 * tex.g) + (3.86714 * tex.b); + float S = (0.0299566 * tex.r) + (0.184309 * tex.g) + (1.46709 * tex.b); + + // Protanopia + float l = 0.0 * L + 2.02344 * M + -2.52581 * S; + float m = 0.0 * L + 1.0 * M + 0.0 * S; + float s = 0.0 * L + 0.0 * M + 1.0 * S; + + vec4 error; + error.r = (0.0809444479 * l) + (-0.130504409 * m) + (0.116721066 * s); + error.g = (-0.0102485335 * l) + (0.0540193266 * m) + (-0.113614708 * s); + error.b = (-0.000365296938 * l) + (-0.00412161469 * m) + (0.693511405 * s); + error.a = 1.0; + vec4 diff = tex - error; + vec4 correction; + correction.r = 0.0; + correction.g = (diff.r * 0.7) + (diff.g * 1.0); + correction.b = (diff.r * 0.7) + (diff.b * 1.0); + correction = tex + correction; + + fragColor = correction * modulation; +} diff --git a/src/plugins/colorblindnesscorrection/shaders/README b/src/plugins/colorblindnesscorrection/shaders/README new file mode 100644 index 0000000000..19833ecc41 --- /dev/null +++ b/src/plugins/colorblindnesscorrection/shaders/README @@ -0,0 +1,8 @@ +ColorBlindness correction shader with adjustable intensity. Can correct for: +* Protanopia (Greatly reduced reds) +* Deuteranopia (Greatly reduced greens) +* Tritanopia (Greatly reduced blues) + +The correction algorithm is taken from http://www.daltonize.org/search/label/Daltonize + +This shader is released under the CC0 license. Feel free to use, improve and change this shader and consider sharing the modified result. diff --git a/src/plugins/colorblindnesscorrection/shaders/Tritanopia.frag b/src/plugins/colorblindnesscorrection/shaders/Tritanopia.frag new file mode 100644 index 0000000000..7ae73fbe3d --- /dev/null +++ b/src/plugins/colorblindnesscorrection/shaders/Tritanopia.frag @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: None +// SPDX-License-Identifier: CC0-1.0 +uniform sampler2D sampler; +uniform vec4 modulation; +uniform float saturation; +varying vec2 texcoord0; + +in vec2 texcoord0; +out vec4 fragColor; + +void main() +{ + vec4 tex = texture2D(sampler, texcoord0); + + if (saturation != 1.0) { + vec3 desaturated = tex.rgb * vec3( 0.30, 0.59, 0.11 ); + desaturated = vec3(dot( desaturated, tex.rgb )); + tex.rgb = tex.rgb * vec3(saturation) + desaturated * vec3(1.0 - saturation); + } + + float L = (17.8824 * tex.r) + (43.5161 * tex.g) + (4.11935 * tex.b); + float M = (3.45565 * tex.r) + (27.1554 * tex.g) + (3.86714 * tex.b); + float S = (0.0299566 * tex.r) + (0.184309 * tex.g) + (1.46709 * tex.b); + + // Tritanopia + float l = 1.0 * L + 0.0 * M + 0.0 * S; + float m = 0.0 * L + 1.0 * M + 0.0 * S; + float s = -0.395913 * L + 0.801109 * M + 0.0 * S; + + vec4 error; + error.r = (0.0809444479 * l) + (-0.130504409 * m) + (0.116721066 * s); + error.g = (-0.0102485335 * l) + (0.0540193266 * m) + (-0.113614708 * s); + error.b = (-0.000365296938 * l) + (-0.00412161469 * m) + (0.693511405 * s); + error.a = 1.0; + vec4 diff = tex - error; + vec4 correction; + correction.r = 0.0; + correction.g = (diff.r * 0.7) + (diff.g * 1.0); + correction.b = (diff.r * 0.7) + (diff.b * 1.0); + correction = tex + correction; + + gl_FragColor = correction * modulation; +} diff --git a/src/plugins/colorblindnesscorrection/shaders/Tritanopia_core.frag b/src/plugins/colorblindnesscorrection/shaders/Tritanopia_core.frag new file mode 100644 index 0000000000..f2ec6448a3 --- /dev/null +++ b/src/plugins/colorblindnesscorrection/shaders/Tritanopia_core.frag @@ -0,0 +1,43 @@ +#version 140 +// SPDX-FileCopyrightText: None +// SPDX-License-Identifier: CC0-1.0 +uniform sampler2D sampler; +uniform vec4 modulation; +uniform float saturation; + +in vec2 texcoord0; +out vec4 fragColor; + +void main() +{ + vec4 tex = texture2D(sampler, texcoord0); + + if (saturation != 1.0) { + vec3 desaturated = tex.rgb * vec3( 0.30, 0.59, 0.11 ); + desaturated = vec3(dot( desaturated, tex.rgb )); + tex.rgb = tex.rgb * vec3(saturation) + desaturated * vec3(1.0 - saturation); + } + + float L = (17.8824 * tex.r) + (43.5161 * tex.g) + (4.11935 * tex.b); + float M = (3.45565 * tex.r) + (27.1554 * tex.g) + (3.86714 * tex.b); + float S = (0.0299566 * tex.r) + (0.184309 * tex.g) + (1.46709 * tex.b); + + // Tritanopia + float l = 1.0 * L + 0.0 * M + 0.0 * S; + float m = 0.0 * L + 1.0 * M + 0.0 * S; + float s = -0.395913 * L + 0.801109 * M + 0.0 * S; + + vec4 error; + error.r = (0.0809444479 * l) + (-0.130504409 * m) + (0.116721066 * s); + error.g = (-0.0102485335 * l) + (0.0540193266 * m) + (-0.113614708 * s); + error.b = (-0.000365296938 * l) + (-0.00412161469 * m) + (0.693511405 * s); + error.a = 1.0; + vec4 diff = tex - error; + vec4 correction; + correction.r = 0.0; + correction.g = (diff.r * 0.7) + (diff.g * 1.0); + correction.b = (diff.r * 0.7) + (diff.b * 1.0); + correction = tex + correction; + + fragColor = correction * modulation; +} diff --git a/src/plugins/colorblindnesscorrection/ui/main.qml b/src/plugins/colorblindnesscorrection/ui/main.qml new file mode 100644 index 0000000000..e27ea4d5de --- /dev/null +++ b/src/plugins/colorblindnesscorrection/ui/main.qml @@ -0,0 +1,83 @@ +/* + SPDX-FileCopyrightText: 2023 Fushan Wen + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +import QtQuick +import QtQuick.Controls as QQC +import QtQuick.Layouts +import org.kde.kirigami as Kirigami + +import org.kde.kcmutils as KCM +import org.kde.plasma.kwin.colorblindnesscorrectioneffect.kcm + +KCM.SimpleKCM { + id: root + + implicitWidth: Kirigami.Units.gridUnit * 30 + implicitHeight: Kirigami.Units.gridUnit * 22 + + RowLayout { + id: previewArea + Layout.fillWidth: true + spacing: Kirigami.Units.smallSpacing + + Item { + Layout.fillWidth: true + } + + Repeater { + model: [ + { name: i18n("Red"), colors: ["Red", "Orange", "Yellow"] }, + { name: i18n("Green"), colors: ["Green", "LimeGreen", "Lime"] }, + { name: i18n("Blue"), colors: ["Blue", "DeepSkyBlue", "Aqua"] }, + { name: i18n("Purple"), colors: ["Purple", "Fuchsia", "Violet"] }, + ] + + delegate: Column { + spacing: 0 + + Repeater { + model: modelData.colors + delegate: Rectangle { + width: Kirigami.Units.gridUnit * 5 + height: Kirigami.Units.gridUnit * 5 + color: modelData + } + } + + QQC.Label { + anchors.horizontalCenter: parent.horizontalCenter + text: modelData.name + } + } + } + + Item { + Layout.fillWidth: true + } + } + + Kirigami.FormLayout { + id: formLayout + anchors { + top: previewArea.bottom + topMargin: Kirigami.Units.largeSpacing + } + + QQC.ComboBox { + Kirigami.FormData.label: i18nc("@label", "Mode:") + currentIndex: kcm.settings.mode + textRole: "text" + valueRole: "value" + model: [ + { value: 0, text: i18nc("@option", "Protanopia (red weak)") }, + { value: 1, text: i18nc("@option", "Deuteranopia (green weak)") }, + { value: 2, text: i18nc("@option", "Tritanopia (blue-yellow)") }, + ] + + onActivated: kcm.settings.mode = currentValue + } + } +}