[gnome-shell] screenshot: Add preview to color picker
- From: Georges Basile Stavracas Neto <gbsneto src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell] screenshot: Add preview to color picker
- Date: Fri, 22 May 2020 14:47:47 +0000 (UTC)
commit f06223df4849c9ed6480c4a07bc9fc9727954458
Author: Florian Müllner <fmuellner gnome org>
Date: Mon Jul 30 18:31:17 2018 +0200
screenshot: Add preview to color picker
With color picking implemented in the compositor, we
can do better than letting the user pick a pixel with
the crosshair cursor, and present them with a preview
of the color that will be selected.
Do this by replacing the cursor with a custom icon and
apply a recoloring effect, where we replace a given color
with the color of the currently hovered pixel (similar
to a green screen).
https://gitlab.gnome.org/GNOME/gnome-shell/issues/451
data/gnome-shell-theme.gresource.xml | 1 +
data/theme/color-pick.svg | 94 ++++++++++++++++
js/ui/screenshot.js | 204 ++++++++++++++++++++++++++++++++---
3 files changed, 284 insertions(+), 15 deletions(-)
---
diff --git a/data/gnome-shell-theme.gresource.xml b/data/gnome-shell-theme.gresource.xml
index ac9c3fd5e1..d15c45c078 100644
--- a/data/gnome-shell-theme.gresource.xml
+++ b/data/gnome-shell-theme.gresource.xml
@@ -6,6 +6,7 @@
<file>checkbox-off-focused.svg</file>
<file>checkbox-off.svg</file>
<file>checkbox.svg</file>
+ <file alias="icons/color-pick.svg">color-pick.svg</file>
<file>dash-placeholder.svg</file>
<file>gnome-shell.css</file>
<file>gnome-shell-high-contrast.css</file>
diff --git a/data/theme/color-pick.svg b/data/theme/color-pick.svg
new file mode 100644
index 0000000000..d9af69023e
--- /dev/null
+++ b/data/theme/color-pick.svg
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="5.4116011mm"
+ height="5.1374583mm"
+ viewBox="0 0 5.4116011 5.1374583"
+ version="1.1"
+ id="svg5595"
+ inkscape:version="0.92.4 (unknown)"
+ sodipodi:docname="color-pick.svg">
+ <defs
+ id="defs5589">
+ <filter
+ inkscape:collect="always"
+ x="-0.10291173"
+ width="1.2058235"
+ y="-0.065432459"
+ height="1.1308649"
+ id="filter5601"
+ style="color-interpolation-filters:sRGB">
+ <feGaussianBlur
+ inkscape:collect="always"
+ stdDeviation="0.610872"
+ id="feGaussianBlur5603" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="15.839192"
+ inkscape:cx="39.387731"
+ inkscape:cy="12.554326"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="1920"
+ inkscape:window-height="1016"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0" />
+ <metadata
+ id="metadata5592">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-103.12753,-146.26461)">
+ <circle
+ r="8.4810486"
+ cy="9.82623"
+ cx="10.226647"
+ id="circle7584"
+
style="color:#000000;display:inline;overflow:visible;opacity:0.6;vector-effect:none;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;filter:url(#filter5601)"
+ transform="matrix(0.26458333,0,0,0.26458333,103.12753,146.26461)" />
+ <path
+
style="color:#000000;display:inline;overflow:visible;opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:0.26399338;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal"
+ d="m 108.07728,148.64122 c 0,1.2393 -1.00465,2.24394 -2.24395,2.24394 -1.23929,0 -2.24716,-1.00465
-2.25221,-2.24394 l -0.009,-2.24458 2.26136,6.4e-4 c 1.2393,3.4e-4 2.24395,1.00464 2.24395,2.24394 z"
+ id="path7523-7"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ssscss" />
+ <circle
+
style="color:#000000;display:inline;overflow:visible;opacity:1;vector-effect:none;fill:#50dbb5;fill-opacity:1;stroke:none;stroke-width:0.36885914;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal"
+ id="path7482-1"
+ cx="105.83707"
+ cy="148.64352"
+ r="1.844296" />
+ </g>
+</svg>
diff --git a/js/ui/screenshot.js b/js/ui/screenshot.js
index 0fe5fdff01..7be9ba051a 100644
--- a/js/ui/screenshot.js
+++ b/js/ui/screenshot.js
@@ -1,7 +1,7 @@
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported ScreenshotService */
-const { Clutter, Graphene, Gio, GObject, GLib, Meta, Shell, St } = imports.gi;
+const { Clutter, Gio, GObject, GLib, Meta, Shell, St } = imports.gi;
const GrabHelper = imports.ui.grabHelper;
const Lightbox = imports.ui.lightbox;
@@ -259,15 +259,13 @@ var ScreenshotService = class {
}
async PickColorAsync(params, invocation) {
- let pickPixel = new PickPixel();
- try {
- const coords = await pickPixel.pickAsync();
-
- let screenshot = this._createScreenshot(invocation, false);
- if (!screenshot)
- return;
+ const screenshot = this._createScreenshot(invocation, false);
+ if (!screenshot)
+ return;
- const [color] = await screenshot.pick_color(coords.x, coords.y);
+ const pickPixel = new PickPixel(screenshot);
+ try {
+ const color = await pickPixel.pickAsync();
const { red, green, blue } = color;
const retval = GLib.Variant.new('(a{sv})', [{
color: GLib.Variant.new('(ddd)', [
@@ -379,12 +377,145 @@ class SelectArea extends St.Widget {
}
});
+var RecolorEffect = GObject.registerClass({
+ Properties: {
+ color: GObject.ParamSpec.boxed(
+ 'color', 'color', 'replacement color',
+ GObject.ParamFlags.WRITABLE,
+ Clutter.Color.$gtype),
+ chroma: GObject.ParamSpec.boxed(
+ 'chroma', 'chroma', 'color to replace',
+ GObject.ParamFlags.WRITABLE,
+ Clutter.Color.$gtype),
+ threshold: GObject.ParamSpec.float(
+ 'threshold', 'threshold', 'threshold',
+ GObject.ParamFlags.WRITABLE,
+ 0.0, 1.0, 0.0),
+ smoothing: GObject.ParamSpec.float(
+ 'smoothing', 'smoothing', 'smoothing',
+ GObject.ParamFlags.WRITABLE,
+ 0.0, 1.0, 0.0),
+ },
+}, class RecolorEffect extends Shell.GLSLEffect {
+ _init(params) {
+ this._color = new Clutter.Color();
+ this._chroma = new Clutter.Color();
+ this._threshold = 0;
+ this._smoothing = 0;
+
+ this._colorLocation = null;
+ this._chromaLocation = null;
+ this._thresholdLocation = null;
+ this._smoothingLocation = null;
+
+ super._init(params);
+
+ this._colorLocation = this.get_uniform_location('recolor_color');
+ this._chromaLocation = this.get_uniform_location('chroma_color');
+ this._thresholdLocation = this.get_uniform_location('threshold');
+ this._smoothingLocation = this.get_uniform_location('smoothing');
+
+ this._updateColorUniform(this._colorLocation, this._color);
+ this._updateColorUniform(this._chromaLocation, this._chroma);
+ this._updateFloatUniform(this._thresholdLocation, this._threshold);
+ this._updateFloatUniform(this._smoothingLocation, this._smoothing);
+ }
+
+ _updateColorUniform(location, color) {
+ if (!location)
+ return;
+
+ this.set_uniform_float(location,
+ 3, [color.red / 255, color.green / 255, color.blue / 255]);
+ this.queue_repaint();
+ }
+
+ _updateFloatUniform(location, value) {
+ if (!location)
+ return;
+
+ this.set_uniform_float(location, 1, [value]);
+ this.queue_repaint();
+ }
+
+ set color(c) {
+ if (this._color.equal(c))
+ return;
+
+ this._color = c;
+ this.notify('color');
+
+ this._updateColorUniform(this._colorLocation, this._color);
+ }
+
+ set chroma(c) {
+ if (this._chroma.equal(c))
+ return;
+
+ this._chroma = c;
+ this.notify('chroma');
+
+ this._updateColorUniform(this._chromaLocation, this._chroma);
+ }
+
+ set threshold(value) {
+ if (this._threshold === value)
+ return;
+
+ this._threshold = value;
+ this.notify('threshold');
+
+ this._updateFloatUniform(this._thresholdLocation, this._threshold);
+ }
+
+ set smoothing(value) {
+ if (this._smoothing === value)
+ return;
+
+ this._smoothing = value;
+ this.notify('smoothing');
+
+ this._updateFloatUniform(this._smoothingLocation, this._smoothing);
+ }
+
+ vfunc_build_pipeline() {
+ // Conversion parameters from https://en.wikipedia.org/wiki/YCbCr
+ const decl = `
+ vec3 rgb2yCrCb(vec3 c) { \n
+ float y = 0.299 * c.r + 0.587 * c.g + 0.114 * c.b; \n
+ float cr = 0.7133 * (c.r - y); \n
+ float cb = 0.5643 * (c.b - y); \n
+ return vec3(y, cr, cb); \n
+ } \n
+ \n
+ uniform vec3 chroma_color; \n
+ uniform vec3 recolor_color; \n
+ uniform float threshold; \n
+ uniform float smoothing; \n`;
+ const src = `
+ vec3 mask = rgb2yCrCb(chroma_color.rgb); \n
+ vec3 yCrCb = rgb2yCrCb(cogl_color_out.rgb); \n
+ float blend = \n
+ smoothstep(threshold, \n
+ threshold + smoothing, \n
+ distance(yCrCb.gb, mask.gb)); \n
+ cogl_color_out.rgb = \n
+ mix(recolor_color, cogl_color_out.rgb, blend); \n`;
+
+ this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, decl, src, false);
+ }
+});
+
var PickPixel = GObject.registerClass(
class PickPixel extends St.Widget {
- _init() {
+ _init(screenshot) {
super._init({ visible: false, reactive: true });
+ this._screenshot = screenshot;
+
this._result = null;
+ this._color = null;
+ this._inPick = false;
Main.uiGroup.add_actor(this);
@@ -393,16 +524,44 @@ class PickPixel extends St.Widget {
let constraint = new Clutter.BindConstraint({ source: global.stage,
coordinate: Clutter.BindCoordinate.ALL });
this.add_constraint(constraint);
+
+ const action = new Clutter.ClickAction();
+ action.connect('clicked', async () => {
+ await this._pickColor(...action.get_coords());
+ this._result = this._color;
+ this._grabHelper.ungrab();
+ });
+ this.add_action(action);
+
+ this._recolorEffect = new RecolorEffect({
+ chroma: new Clutter.Color({
+ red: 80,
+ green: 219,
+ blue: 181,
+ }),
+ threshold: 0.04,
+ smoothing: 0.07,
+ });
+ this._previewCursor = new St.Icon({
+ icon_name: 'color-pick',
+ icon_size: Meta.prefs_get_cursor_size(),
+ effect: this._recolorEffect,
+ visible: false,
+ });
+ Main.uiGroup.add_actor(this._previewCursor);
}
async pickAsync() {
- global.display.set_cursor(Meta.Cursor.CROSSHAIR);
+ global.display.set_cursor(Meta.Cursor.BLANK);
Main.uiGroup.set_child_above_sibling(this, null);
this.show();
+ this._pickColor(...global.get_pointer());
+
await this._grabHelper.grabAsync({ actor: this });
global.display.set_cursor(Meta.Cursor.DEFAULT);
+ this._previewCursor.destroy();
GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
this.destroy();
@@ -412,10 +571,25 @@ class PickPixel extends St.Widget {
return this._result;
}
- vfunc_button_release_event(buttonEvent) {
- let { x, y } = buttonEvent;
- this._result = new Graphene.Point({ x, y });
- this._grabHelper.ungrab();
+ async _pickColor(x, y) {
+ if (this._inPick)
+ return;
+
+ this._inPick = true;
+ this._previewCursor.set_position(x, y);
+ [this._color] = await this._screenshot.pick_color(x, y);
+ this._inPick = false;
+
+ if (!this._color)
+ return;
+
+ this._recolorEffect.color = this._color;
+ this._previewCursor.show();
+ }
+
+ vfunc_motion_event(motionEvent) {
+ const { x, y } = motionEvent;
+ this._pickColor(x, y);
return Clutter.EVENT_PROPAGATE;
}
});
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]