diff --git a/effects/invert/data/1.10/invert-plus-hue.frag b/effects/invert/data/1.10/invert-plus-hue.frag new file mode 100644 --- /dev/null +++ b/effects/invert/data/1.10/invert-plus-hue.frag @@ -0,0 +1,69 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks +Copyright (C) 2013 Sam Hocevar <> +Copyright (C) 2013 Miguel Vicente Linares +Copyright (C) 2020 Javier O. Cordero PĂ©rez + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + +uniform sampler2D sampler; +uniform vec4 modulation; +uniform float saturation; + +varying vec2 texcoord0; + +// Original functions by Sam Hocevar, licensed CC-BY-SA +// Source: https://gamedev.stackexchange.com/questions/59797/glsl-shader-change-hue-saturation-brightness +vec3 rgb2hsv(vec3 c) +{ + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} +vec3 hsv2rgb(vec3 c) +{ + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +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 ); + } + + // Hue displacement based off the code by Miguel Vicente Linares, licensed CC-BY-SA + // Source: https://gamedev.stackexchange.com/questions/59797/glsl-shader-change-hue-saturation-brightness + vec3 fragHSV = rgb2hsv( vec3(1.0) - tex.rgb ).xyz; + fragHSV.x += 0.5; + fragHSV.xyz = mod(fragHSV.xyz, 1.000001); + tex.rgb = hsv2rgb(fragHSV); + tex *= modulation; + tex.rgb *= tex.a; + + gl_FragColor = tex; +} diff --git a/effects/invert/data/1.10/invert.frag b/effects/invert/data/1.10/invert.frag --- a/effects/invert/data/1.10/invert.frag +++ b/effects/invert/data/1.10/invert.frag @@ -1,3 +1,23 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + uniform sampler2D sampler; uniform vec4 modulation; uniform float saturation; diff --git a/effects/invert/data/1.40/invert-plus-hue.frag b/effects/invert/data/1.40/invert-plus-hue.frag new file mode 100644 --- /dev/null +++ b/effects/invert/data/1.40/invert-plus-hue.frag @@ -0,0 +1,70 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks +Copyright (C) 2020 Javier Cordero + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + +#version 140 +uniform sampler2D sampler; +uniform vec4 modulation; +uniform float saturation; + +in vec2 texcoord0; + +out vec4 fragColor; + +// Original functions by Sam Hocevar, licensed CC-BY-SA +// Source: https://gamedev.stackexchange.com/questions/59797/glsl-shader-change-hue-saturation-brightness +vec3 rgb2hsv(vec3 c) +{ + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); +} +vec3 hsv2rgb(vec3 c) +{ + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +void main() +{ + vec4 tex = texture(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 ); + } + + // Hue displacement based off the code by Miguel Vicente Linares, licensed CC-BY-SA + // Source: https://gamedev.stackexchange.com/questions/59797/glsl-shader-change-hue-saturation-brightness + vec3 fragHSV = rgb2hsv( vec3(1.0) - tex.rgb ).xyz; + fragHSV.x += 0.5; + fragHSV.xyz = mod(fragHSV.xyz, 1.000001); + tex.rgb = hsv2rgb(fragHSV); + tex *= modulation; + tex.rgb *= tex.a; + + fragColor = tex; +} diff --git a/effects/invert/data/1.40/invert.frag b/effects/invert/data/1.40/invert.frag --- a/effects/invert/data/1.40/invert.frag +++ b/effects/invert/data/1.40/invert.frag @@ -1,3 +1,23 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2007 Rivo Laks + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*********************************************************************/ + #version 140 uniform sampler2D sampler; uniform vec4 modulation; diff --git a/effects/invert/invert.h b/effects/invert/invert.h --- a/effects/invert/invert.h +++ b/effects/invert/invert.h @@ -4,6 +4,7 @@ Copyright (C) 2007 Rivo Laks Copyright (C) 2008 Lucas Murray +Copyright (C) 2020 Javier Cordero This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -51,18 +52,23 @@ public Q_SLOTS: void toggleScreenInversion(); - void toggleWindow(); + void toggleWindowInversion(); + void toggleScreenInversionPlusHue(); + void toggleWindowInversionPlusHue(); void slotWindowClosed(KWin::EffectWindow *w); protected: bool loadData(); private: bool m_inited; bool m_valid; - GLShader* m_shader; - bool m_allWindows; - QList m_windows; + GLShader* m_invert_shader; + GLShader* m_invert_plus_hue_shader; + bool m_allWindowsInvert; + bool m_allWindowsInvertPlusHue; + QList m_windows_invert; + QList m_windows_invert_plus_hue; }; inline int InvertEffect::requestedEffectChainPosition() const diff --git a/effects/invert/invert.cpp b/effects/invert/invert.cpp --- a/effects/invert/invert.cpp +++ b/effects/invert/invert.cpp @@ -4,6 +4,7 @@ Copyright (C) 2007 Rivo Laks Copyright (C) 2008 Lucas Murray +Copyright (C) 2020 Javier Cordero This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -37,8 +38,10 @@ InvertEffect::InvertEffect() : m_inited(false), m_valid(true), - m_shader(nullptr), - m_allWindows(false) + m_invert_shader(nullptr), + m_invert_plus_hue_shader(nullptr), + m_allWindowsInvert(false), + m_allWindowsInvertPlusHue(false) { QAction* a = new QAction(this); a->setObjectName(QStringLiteral("Invert")); @@ -54,14 +57,31 @@ KGlobalAccel::self()->setDefaultShortcut(b, QList() << Qt::CTRL + Qt::META + Qt::Key_U); KGlobalAccel::self()->setShortcut(b, QList() << Qt::CTRL + Qt::META + Qt::Key_U); effects->registerGlobalShortcut(Qt::CTRL + Qt::META + Qt::Key_U, b); - connect(b, &QAction::triggered, this, &InvertEffect::toggleWindow); + connect(b, &QAction::triggered, this, &InvertEffect::toggleWindowInversion); + + QAction* c = new QAction(this); + c->setObjectName(QStringLiteral("InvertPlusHue")); + c->setText(i18n("Toggle Invert Plus Hue Effect")); + KGlobalAccel::self()->setDefaultShortcut(c, QList() << Qt::CTRL + Qt::META + Qt::Key_H); + KGlobalAccel::self()->setShortcut(c, QList() << Qt::CTRL + Qt::META + Qt::Key_H); + effects->registerGlobalShortcut(Qt::CTRL + Qt::META + Qt::Key_H, c); + connect(c, &QAction::triggered, this, &InvertEffect::toggleScreenInversionPlusHue); + + QAction* d = new QAction(this); + d->setObjectName(QStringLiteral("InvertPlusHueWindow")); + d->setText(i18n("Toggle Invert Plus Hue Effect on Window")); + KGlobalAccel::self()->setDefaultShortcut(d, QList() << Qt::CTRL + Qt::META + Qt::Key_G); + KGlobalAccel::self()->setShortcut(d, QList() << Qt::CTRL + Qt::META + Qt::Key_G); + effects->registerGlobalShortcut(Qt::CTRL + Qt::META + Qt::Key_G, d); + connect(d, &QAction::triggered, this, &InvertEffect::toggleWindowInversionPlusHue); connect(effects, &EffectsHandler::windowClosed, this, &InvertEffect::slotWindowClosed); } InvertEffect::~InvertEffect() { - delete m_shader; + delete m_invert_shader; + delete m_invert_plus_hue_shader; } bool InvertEffect::supported() @@ -73,9 +93,10 @@ { m_inited = true; - m_shader = ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("invert.frag")); - if (!m_shader->isValid()) { - qCCritical(KWINEFFECTS) << "The shader failed to load!"; + m_invert_shader = ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("invert.frag")); + m_invert_plus_hue_shader = ShaderManager::instance()->generateShaderFromResources(ShaderTrait::MapTexture, QString(), QStringLiteral("invert-plus-hue.frag")); + if (!m_invert_shader->isValid() || !m_invert_plus_hue_shader->isValid()) { + qCCritical(KWINEFFECTS) << "The shaders failed to load!"; return false; } @@ -88,58 +109,139 @@ if (m_valid && !m_inited) m_valid = loadData(); - bool useShader = m_valid && (m_allWindows != m_windows.contains(w)); - if (useShader) { + const bool invertWindow = m_windows_invert.contains(w); + const bool invertPlusHueWindow = m_windows_invert_plus_hue.contains(w); + bool useInvertShader = false; + bool useInvertPlusHueShader = false; + if (m_valid) { + if (m_allWindowsInvert) { + if (invertPlusHueWindow) + useInvertPlusHueShader = true; + else if (!invertWindow) + useInvertShader = true; + } + else if (m_allWindowsInvertPlusHue) { + if (invertWindow) + useInvertShader = true; + else if (!invertPlusHueWindow) + useInvertPlusHueShader = true; + } + else { + if (invertWindow) + useInvertShader = true; + else if (invertPlusHueWindow) + useInvertPlusHueShader = true; + } + } + if (useInvertShader) { + ShaderManager *shaderManager = ShaderManager::instance(); + shaderManager->pushShader(m_invert_shader); + + data.shader = m_invert_shader; + } + else if (useInvertPlusHueShader) { ShaderManager *shaderManager = ShaderManager::instance(); - shaderManager->pushShader(m_shader); + shaderManager->pushShader(m_invert_plus_hue_shader); - data.shader = m_shader; + data.shader = m_invert_plus_hue_shader; } effects->drawWindow(w, mask, region, data); - if (useShader) { + if (useInvertShader || useInvertPlusHueShader) { ShaderManager::instance()->popShader(); } } void InvertEffect::paintEffectFrame(KWin::EffectFrame* frame, const QRegion ®ion, double opacity, double frameOpacity) { - if (m_valid && m_allWindows) { - frame->setShader(m_shader); - ShaderBinder binder(m_shader); - effects->paintEffectFrame(frame, region, opacity, frameOpacity); - } else { - effects->paintEffectFrame(frame, region, opacity, frameOpacity); + if (m_valid) { + if (m_allWindowsInvert) { + frame->setShader(m_invert_shader); + ShaderBinder binder(m_invert_shader); + effects->paintEffectFrame(frame, region, opacity, frameOpacity); + } + else if (m_allWindowsInvertPlusHue) { + frame->setShader(m_invert_plus_hue_shader); + ShaderBinder binder(m_invert_plus_hue_shader); + effects->paintEffectFrame(frame, region, opacity, frameOpacity); + } } + else + effects->paintEffectFrame(frame, region, opacity, frameOpacity); } void InvertEffect::slotWindowClosed(EffectWindow* w) { - m_windows.removeOne(w); + m_windows_invert.removeOne(w); + m_windows_invert_plus_hue.removeOne(w); } -void InvertEffect::toggleScreenInversion() +void InvertEffect::toggleWindowInversion() { - m_allWindows = !m_allWindows; - effects->addRepaintFull(); + if (!effects->activeWindow()) { + return; + } + if (!m_windows_invert.contains(effects->activeWindow())) { + if (m_windows_invert_plus_hue.contains(effects->activeWindow())) + m_windows_invert_plus_hue.removeOne(effects->activeWindow()); + m_windows_invert.append(effects->activeWindow()); + } + else + m_windows_invert.removeOne(effects->activeWindow()); + effects->activeWindow()->addRepaintFull(); } -void InvertEffect::toggleWindow() +void InvertEffect::toggleWindowInversionPlusHue() { if (!effects->activeWindow()) { return; } - if (!m_windows.contains(effects->activeWindow())) - m_windows.append(effects->activeWindow()); + if (!m_windows_invert_plus_hue.contains(effects->activeWindow())) { + if (m_windows_invert.contains(effects->activeWindow())) + m_windows_invert.removeOne(effects->activeWindow()); + m_windows_invert_plus_hue.append(effects->activeWindow()); + } else - m_windows.removeOne(effects->activeWindow()); + m_windows_invert_plus_hue.removeOne(effects->activeWindow()); effects->activeWindow()->addRepaintFull(); } +void InvertEffect::toggleScreenInversion() +{ + m_allWindowsInvert = !m_allWindowsInvert; + // If the user had previously applied an invert plus hue to the screen, they'd probably intend any manually inverted windows + // to continue being inverted. Because windows in the list of windows to be inverted are treated as windows to not be inverted + // toggling between screen inverts will make any previously inverted window of the same kind of invert stop being inverted, + // which is undesired. A simple solution that prevents this is to swap the two groups of individually inverted windows. + if (m_allWindowsInvertPlusHue) { + m_allWindowsInvertPlusHue = false; + QList invertedWindows = m_windows_invert_plus_hue; + m_windows_invert_plus_hue = m_windows_invert; + m_windows_invert = invertedWindows; + } + effects->addRepaintFull(); +} + +void InvertEffect::toggleScreenInversionPlusHue() +{ + m_allWindowsInvertPlusHue = !m_allWindowsInvertPlusHue; + // If the user had previously applied an invert to the screen, they'd probably intend any manually inverted windows + // to continue being inverted. Because windows in the list of windows to be inverted are treated as windows to not be inverted + // toggling between screen inverts will make any previously inverted window of the same kind of invert stop being inverted, + // which is undesired. A simple solution that prevents this is to swap the two groups of individually inverted windows. + if (m_allWindowsInvert) { + m_allWindowsInvert = false; + QList invertedWindows = m_windows_invert; + m_windows_invert = m_windows_invert_plus_hue; + m_windows_invert_plus_hue = invertedWindows; + } + effects->addRepaintFull(); +} + bool InvertEffect::isActive() const { - return m_valid && (m_allWindows || !m_windows.isEmpty()); + return m_valid && (m_allWindowsInvert || m_allWindowsInvertPlusHue || !m_windows_invert.isEmpty() || !m_windows_invert_plus_hue.isEmpty()); } bool InvertEffect::provides(Feature f) diff --git a/effects/invert/invert_config.cpp b/effects/invert/invert_config.cpp --- a/effects/invert/invert_config.cpp +++ b/effects/invert/invert_config.cpp @@ -3,6 +3,7 @@ This file is part of the KDE project. Copyright (C) 2007 Rivo Laks +Copyright (C) 2020 Javier Cordero This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -60,6 +61,18 @@ KGlobalAccel::self()->setDefaultShortcut(b, QList() << Qt::CTRL + Qt::META + Qt::Key_U); KGlobalAccel::self()->setShortcut(b, QList() << Qt::CTRL + Qt::META + Qt::Key_U); + QAction* c = actionCollection->addAction(QStringLiteral("InvertPlusHue")); + c->setText(i18n("Toggle Invert Plus Hue Effect")); + c->setProperty("isConfigurationAction", true); + KGlobalAccel::self()->setDefaultShortcut(c, QList() << Qt::CTRL + Qt::META + Qt::Key_H); + KGlobalAccel::self()->setShortcut(c, QList() << Qt::CTRL + Qt::META + Qt::Key_H); + + QAction* d = actionCollection->addAction(QStringLiteral("InvertPlusHueWindow")); + d->setText(i18n("Toggle Invert Plus Hue Effect on Window")); + d->setProperty("isConfigurationAction", true); + KGlobalAccel::self()->setDefaultShortcut(d, QList() << Qt::CTRL + Qt::META + Qt::Key_G); + KGlobalAccel::self()->setShortcut(d, QList() << Qt::CTRL + Qt::META + Qt::Key_G); + mShortcutEditor = new KShortcutsEditor(actionCollection, this, KShortcutsEditor::GlobalAction, KShortcutsEditor::LetterShortcutsDisallowed); connect(mShortcutEditor, &KShortcutsEditor::keyChange, this, &InvertEffectConfig::markAsChanged); diff --git a/effects/shaders.qrc b/effects/shaders.qrc --- a/effects/shaders.qrc +++ b/effects/shaders.qrc @@ -6,6 +6,7 @@ cube/data/1.10/cylinder.vert cube/data/1.10/sphere.vert invert/data/1.10/invert.frag + invert/data/1.10/invert-plus-hue.frag lookingglass/data/1.10/lookingglass.frag startupfeedback/data/1.10/blinking-startup-fragment.glsl @@ -16,6 +17,7 @@ cube/data/1.40/cylinder.vert cube/data/1.40/sphere.vert invert/data/1.40/invert.frag + invert/data/1.40/invert-plus-hue.frag lookingglass/data/1.40/lookingglass.frag startupfeedback/data/1.40/blinking-startup-fragment.glsl