Inkscape
Vector Graphics Editor
color-scales.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later/*
5 * Authors:
6 * see git history
7 * bulia byak <buliabyak@users.sf.net>
8 * Massinissa Derriche <massinissa.derriche@gmail.com>
9 *
10 * Copyright (C) 2018, 2021 Authors
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
15
16#include <functional>
17#include <stdexcept>
18
19#include <glibmm/i18n.h>
20#include <glibmm/ustring.h>
21#include <gtkmm/adjustment.h>
22#include <gtkmm/expander.h>
23#include <gtkmm/grid.h>
24#include <gtkmm/image.h>
25#include <gtkmm/label.h>
26#include <gtkmm/spinbutton.h>
27
28#include "oklab.h"
29#include "preferences.h"
30#include "ui/dialog-events.h"
31#include "ui/icon-loader.h"
32#include "ui/pack.h"
33#include "ui/selected-color.h"
38
39constexpr static int CSC_CHANNEL_R = (1 << 0);
40constexpr static int CSC_CHANNEL_G = (1 << 1);
41constexpr static int CSC_CHANNEL_B = (1 << 2);
42constexpr static int CSC_CHANNEL_A = (1 << 3);
43constexpr static int CSC_CHANNEL_H = (1 << 0);
44constexpr static int CSC_CHANNEL_S = (1 << 1);
45constexpr static int CSC_CHANNEL_V = (1 << 2);
46constexpr static int CSC_CHANNEL_C = (1 << 0);
47constexpr static int CSC_CHANNEL_M = (1 << 1);
48constexpr static int CSC_CHANNEL_Y = (1 << 2);
49constexpr static int CSC_CHANNEL_K = (1 << 3);
50constexpr static int CSC_CHANNEL_CMYKA = (1 << 4);
51
52constexpr static int CSC_CHANNELS_ALL = 0;
53
54constexpr static int XPAD = 2;
55constexpr static int YPAD = 2;
56
57namespace Inkscape::UI::Widget {
58
59static guchar const *sp_color_scales_hue_map();
60static guchar const *sp_color_scales_hsluv_map(guchar *map,
61 std::function<void(float*, float)> callback);
62
63static const char* color_mode_icons[] = {
64 nullptr,
65 "color-selector-rgb",
66 "color-selector-hsx",
67 "color-selector-cmyk",
68 "color-selector-hsx",
69 "color-selector-hsluv",
70 "color-selector-okhsl",
71 "color-selector-cms",
72 nullptr
73};
74
75const char* color_mode_name[] = {
76 N_("None"), N_("RGB"), N_("HSL"), N_("CMYK"), N_("HSV"), N_("HSLuv"), N_("OKHSL"), N_("CMS"), nullptr
77};
78
80 auto index = static_cast<size_t>(mode);
81 assert(index > 0 && index < (sizeof(color_mode_icons) / sizeof(color_mode_icons[0])));
82 return color_mode_icons[index];
83}
84
86 auto index = static_cast<size_t>(mode);
87 assert(index > 0 && index < (sizeof(color_mode_name) / sizeof(color_mode_name[0])));
88 return color_mode_name[index];
89}
90
91std::unique_ptr<Inkscape::UI::ColorSelectorFactory> get_factory(SPColorScalesMode mode) {
92 switch (mode) {
93 case SPColorScalesMode::RGB: return std::make_unique<ColorScalesFactory<SPColorScalesMode::RGB>>();
94 case SPColorScalesMode::HSL: return std::make_unique<ColorScalesFactory<SPColorScalesMode::HSL>>();
95 case SPColorScalesMode::HSV: return std::make_unique<ColorScalesFactory<SPColorScalesMode::HSV>>();
96 case SPColorScalesMode::CMYK: return std::make_unique<ColorScalesFactory<SPColorScalesMode::CMYK>>();
97 case SPColorScalesMode::HSLUV: return std::make_unique<ColorScalesFactory<SPColorScalesMode::HSLUV>>();
98 case SPColorScalesMode::OKLAB: return std::make_unique<ColorScalesFactory<SPColorScalesMode::OKLAB>>();
99 case SPColorScalesMode::CMS: return std::make_unique<ColorICCSelectorFactory>();
100 default:
101 throw std::invalid_argument("There's no factory for the requested color mode");
102 }
103}
104
105std::vector<ColorPickerDescription> get_color_pickers() {
106 std::vector<ColorPickerDescription> pickers;
107
108 for (auto mode : {
116 }) {
117 auto label = get_color_mode_label(mode);
118
119 pickers.emplace_back(ColorPickerDescription {
120 mode,
122 label,
123 Glib::ustring::format("/colorselector/", label, "/visible"),
125 });
126 }
127
128 return pickers;
129}
130
131
132template <SPColorScalesMode MODE>
133gchar const *ColorScales<MODE>::SUBMODE_NAMES[] = { N_("None"), N_("RGB"), N_("HSL"),
134 N_("CMYK"), N_("HSV"), N_("HSLuv"), N_("OKHSL") };
135
136// Preference name for the saved state of toggle-able color wheel
137template <>
139 "/wheel_vis_hsl";
140
141template <>
143 "/wheel_vis_hsv";
144
145template <>
147 "/wheel_vis_hsluv";
148
149template <>
151 "/wheel_vis_okhsl";
152
153template <SPColorScalesMode MODE>
155 : Gtk::Box()
156 , _color(color)
157 , _range_limit(255.0)
158 , _updating(false)
159 , _dragging(false)
160 , _wheel(nullptr)
161{
162 for (gint i = 0; i < 5; i++) {
163 _l[i] = nullptr;
164 _s[i] = nullptr;
165 _b[i] = nullptr;
166 }
167
168 _initUI(no_alpha);
169
170 _color_changed = _color.signal_changed.connect([this](){ _onColorChanged(); });
171 _color_dragged = _color.signal_dragged.connect([this](){ _onColorChanged(); });
172}
173
174template <SPColorScalesMode MODE>
175void ColorScales<MODE>::_initUI(bool no_alpha)
176{
177 set_orientation(Gtk::Orientation::VERTICAL);
178
179 Gtk::Expander *wheel_frame = nullptr;
180
181 if constexpr (
186 {
187 /* Create wheel */
188 if constexpr (MODE == SPColorScalesMode::HSLUV) {
189 _wheel = Gtk::make_managed<Inkscape::UI::Widget::ColorWheelHSLuv>();
190 } else if constexpr (MODE == SPColorScalesMode::OKLAB) {
191 _wheel = Gtk::make_managed<OKWheel>();
192 } else {
193 _wheel = Gtk::make_managed<Inkscape::UI::Widget::ColorWheelHSL>();
194 }
195
196 _wheel->set_visible(true);
197 _wheel->set_halign(Gtk::Align::FILL);
198 _wheel->set_valign(Gtk::Align::FILL);
199 _wheel->set_hexpand(true);
200 _wheel->set_vexpand(true);
201 _wheel->set_name("ColorWheel");
202 _wheel->set_size_request(-1, 130); // minimal size
203
204 /* Signal */
205 _wheel->connect_color_changed([this](){ _wheelChanged(); });
206
207 /* Expander */
208 // Label icon
209 auto const expander_icon = Gtk::manage(
210 sp_get_icon_image("color-wheel", Gtk::IconSize::NORMAL)
211 );
212 expander_icon->set_visible(true);
213 expander_icon->set_margin_start(2 * XPAD);
214 expander_icon->set_margin_end(3 * XPAD);
215 // Label
216 auto const expander_label = Gtk::make_managed<Gtk::Label>(_("Color Wheel"));
217 expander_label->set_visible(true);
218 // Content
219 auto const expander_box = Gtk::make_managed<Gtk::Box>();
220 expander_box->set_visible(true);
221 UI::pack_start(*expander_box, *expander_icon);
222 UI::pack_start(*expander_box, *expander_label);
223 expander_box->set_orientation(Gtk::Orientation::HORIZONTAL);
224 // Expander
225 wheel_frame = Gtk::make_managed<Gtk::Expander>();
226 wheel_frame->set_visible(true);
227 wheel_frame->set_margin_start(2 * XPAD);
228 wheel_frame->set_margin_end(XPAD);
229 wheel_frame->set_margin_top(2 * YPAD);
230 wheel_frame->set_margin_bottom(2 * YPAD);
231 wheel_frame->set_halign(Gtk::Align::FILL);
232 wheel_frame->set_valign(Gtk::Align::FILL);
233 wheel_frame->set_hexpand(true);
234 wheel_frame->set_vexpand(false);
235 wheel_frame->set_label_widget(*expander_box);
236
237 // Signal
238 wheel_frame->property_expanded().signal_changed().connect([=](){
239 bool visible = wheel_frame->get_expanded();
240 wheel_frame->set_vexpand(visible);
241
242 // Save wheel visibility
243 Inkscape::Preferences::get()->setBool(_prefs + _pref_wheel_visibility, visible);
244 });
245
246 wheel_frame->set_child(*_wheel);
247 append(*wheel_frame);
248 }
249
250 /* Create sliders */
251 auto const grid = Gtk::make_managed<Gtk::Grid>();
252 append(*grid);
253
254 for (int i = 0; i < 5; i++) {
255 /* Label */
256 _l[i] = Gtk::make_managed<Gtk::Label>("", true);
257
258 _l[i]->set_halign(Gtk::Align::START);
259 _l[i]->set_visible(true);
260
261 _l[i]->set_margin_start(2 * XPAD);
262 _l[i]->set_margin_end(XPAD);
263 _l[i]->set_margin_top(YPAD);
264 _l[i]->set_margin_bottom(YPAD);
265 grid->attach(*_l[i], 0, i, 1, 1);
266
267 /* Adjustment */
268 _a.push_back(Gtk::Adjustment::create(0.0, 0.0, _range_limit, 1.0, 10.0, 10.0));
269 /* Slider */
270 _s[i] = Gtk::make_managed<Inkscape::UI::Widget::ColorSlider>(_a[i]);
271 _s[i]->set_visible(true);
272
273 _s[i]->set_margin_start(XPAD);
274 _s[i]->set_margin_end(XPAD);
275 _s[i]->set_margin_top(YPAD);
276 _s[i]->set_margin_bottom(YPAD);
277 _s[i]->set_hexpand(true);
278 grid->attach(*_s[i], 1, i, 1, 1);
279
280 /* Spinbutton */
281 _b[i] = Gtk::make_managed<Gtk::SpinButton>(_a[i], 1.0);
283 _l[i]->set_mnemonic_widget(*_b[i]);
284 _b[i]->set_visible(true);
285
286 _b[i]->set_margin_start(XPAD);
287 _b[i]->set_margin_end(XPAD);
288 _b[i]->set_margin_top(YPAD);
289 _b[i]->set_margin_bottom(YPAD);
290 _b[i]->set_halign(Gtk::Align::END);
291 _b[i]->set_valign(Gtk::Align::CENTER);
292 grid->attach(*_b[i], 2, i, 1, 1);
293
294 /* Signals */
295 _a[i]->signal_value_changed().connect([this, i](){ _adjustmentChanged(i); });
296 _s[i]->signal_grabbed.connect([this](){ _sliderAnyGrabbed(); });
297 _s[i]->signal_released.connect([this](){ _sliderAnyReleased(); });
298 _s[i]->signal_value_changed.connect([this](){ _sliderAnyChanged(); });
299 }
300
301 setupMode(no_alpha);
302
303 if constexpr (
308 {
309 // Restore the visibility of the wheel
310 bool visible = Inkscape::Preferences::get()->getBool(_prefs + _pref_wheel_visibility,
311 false);
312 wheel_frame->set_expanded(visible);
313 wheel_frame->set_vexpand(visible);
314 }
315}
316
317template <SPColorScalesMode MODE>
319{
320 SPColor color;
321 gfloat alpha = 1.0;
322 gfloat c[5];
323
324 if constexpr (
330 {
331 _getRgbaFloatv(c);
332 color.set(c[0], c[1], c[2]);
333 alpha = c[3];
334 } else if constexpr (MODE == SPColorScalesMode::CMYK) {
335 _getCmykaFloatv(c);
336
337 float rgb[3];
338 SPColor::cmyk_to_rgb_floatv(rgb, c[0], c[1], c[2], c[3]);
339 color.set(rgb[0], rgb[1], rgb[2]);
340 alpha = c[4];
341 } else {
342 g_warning("file %s: line %d: Illegal color selector mode NONE", __FILE__, __LINE__);
343 }
344
345 _color.setColorAlpha(color, alpha);
346}
347
348template <SPColorScalesMode MODE>
350{
351#ifdef DUMP_CHANGE_INFO
352 g_message("ColorScales::_onColorChanged( this=%p, %f, %f, %f, %f) %d", this,
353 _color.color().v.c[0],
354 _color.color().v.c[1], _color.color().v.c[2], _color.alpha(), int(update_wheel);
355#endif
356
357 gfloat tmp[3];
358 gfloat c[5] = { 0.0, 0.0, 0.0, 0.0 };
359
360 SPColor color = _color.color();
361
362 if constexpr (MODE == SPColorScalesMode::RGB) {
363 color.get_rgb_floatv(c);
364 c[3] = _color.alpha();
365 c[4] = 0.0;
366 } else if constexpr (MODE == SPColorScalesMode::HSL) {
367 color.get_rgb_floatv(tmp);
368 SPColor::rgb_to_hsl_floatv(c, tmp[0], tmp[1], tmp[2]);
369 c[3] = _color.alpha();
370 c[4] = 0.0;
371 // N.B. We setRgb() with emit = false, to avoid a warning from PaintSelector.
372 if (update_wheel) { _wheel->setRgb(tmp[0], tmp[1], tmp[2], true, false); }
373 } else if constexpr (MODE == SPColorScalesMode::HSV) {
374 color.get_rgb_floatv(tmp);
375 SPColor::rgb_to_hsv_floatv(c, tmp[0], tmp[1], tmp[2]);
376 c[3] = _color.alpha();
377 c[4] = 0.0;
378 if (update_wheel) { _wheel->setRgb(tmp[0], tmp[1], tmp[2], true, false); }
379 } else if constexpr (MODE == SPColorScalesMode::CMYK) {
380 color.get_cmyk_floatv(c);
381 c[4] = _color.alpha();
382 } else if constexpr (MODE == SPColorScalesMode::HSLUV) {
383 color.get_rgb_floatv(tmp);
384 SPColor::rgb_to_hsluv_floatv(c, tmp[0], tmp[1], tmp[2]);
385 c[3] = _color.alpha();
386 c[4] = 0.0;
387 if (update_wheel) { _wheel->setRgb(tmp[0], tmp[1], tmp[2], true, false); }
388 } else if constexpr (MODE == SPColorScalesMode::OKLAB) {
389 color.get_rgb_floatv(tmp);
390 // OKLab color space is more sensitive to numerical errors; use doubles.
391 auto const hsl = Oklab::oklab_to_okhsl(Oklab::rgb_to_oklab({tmp[0], tmp[1], tmp[2]}));
392 _updating = true;
393 for (size_t i : {0, 1, 2}) {
394 setScaled(_a[i], hsl[i]);
395 }
396 setScaled(_a[3], _color.alpha());
397 setScaled(_a[4], 0.0);
398 _updateSliders(CSC_CHANNELS_ALL);
399 _updating = false;
400 if (update_wheel) {
401 _wheel->setRgb(tmp[0], tmp[1], tmp[2], true, false);
402 }
403 return;
404 } else {
405 g_warning("file %s: line %d: Illegal color selector mode NONE", __FILE__, __LINE__);
406 }
407
408 _updating = true;
409 setScaled(_a[0], c[0]);
410 setScaled(_a[1], c[1]);
411 setScaled(_a[2], c[2]);
412 setScaled(_a[3], c[3]);
413 setScaled(_a[4], c[4]);
414 _updateSliders(CSC_CHANNELS_ALL);
415 _updating = false;
416}
417
418/* Helpers for setting color value */
419template <SPColorScalesMode MODE>
420double ColorScales<MODE>::getScaled(Glib::RefPtr<Gtk::Adjustment> const &a)
421{
422 return a->get_value() / a->get_upper();
423}
424
425template <SPColorScalesMode MODE>
426void ColorScales<MODE>::setScaled(Glib::RefPtr<Gtk::Adjustment> &a, double v, bool constrained)
427{
428 auto upper = a->get_upper();
429 double val = v * upper;
430 if (constrained) {
431 // TODO: do we want preferences for these?
432 if (upper == 255) {
433 val = round(val/16) * 16;
434 } else {
435 val = round(val/10) * 10;
436 }
437 }
438 a->set_value(val);
439}
440
441template <SPColorScalesMode MODE>
443{
444 _range_limit = upper;
445 for (auto & i : _a) {
446 i->set_upper(upper);
447 }
448}
449
450template <SPColorScalesMode MODE>
452{
453 if (!get_visible()) { return; }
454
455 _updateDisplay();
456}
457
458template <SPColorScalesMode MODE>
460{
461 Gtk::Box::on_show();
462
463 _updateDisplay();
464}
465
466template <SPColorScalesMode MODE>
468{
469 g_return_if_fail(rgba != nullptr);
470
471 if constexpr (MODE == SPColorScalesMode::RGB) {
472 rgba[0] = getScaled(_a[0]);
473 rgba[1] = getScaled(_a[1]);
474 rgba[2] = getScaled(_a[2]);
475 rgba[3] = getScaled(_a[3]);
476 } else if constexpr (MODE == SPColorScalesMode::HSL) {
477 SPColor::hsl_to_rgb_floatv(rgba, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]));
478 rgba[3] = getScaled(_a[3]);
479 } else if constexpr (MODE == SPColorScalesMode::HSV) {
480 SPColor::hsv_to_rgb_floatv(rgba, getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]));
481 rgba[3] = getScaled(_a[3]);
482 } else if constexpr (MODE == SPColorScalesMode::CMYK) {
483 SPColor::cmyk_to_rgb_floatv(rgba, getScaled(_a[0]), getScaled(_a[1]),
484 getScaled(_a[2]), getScaled(_a[3]));
485 rgba[3] = getScaled(_a[4]);
486 } else if constexpr (MODE == SPColorScalesMode::HSLUV) {
487 SPColor::hsluv_to_rgb_floatv(rgba, getScaled(_a[0]), getScaled(_a[1]),
488 getScaled(_a[2]));
489 rgba[3] = getScaled(_a[3]);
490 } else if constexpr (MODE == SPColorScalesMode::OKLAB) {
491 auto const tmp = Oklab::oklab_to_rgb(
492 Oklab::okhsl_to_oklab({ getScaled(_a[0]),
493 getScaled(_a[1]),
494 getScaled(_a[2]) }));
495 for (size_t i : {0, 1, 2}) {
496 rgba[i] = static_cast<float>(tmp[i]);
497 }
498 rgba[3] = getScaled(_a[3]);
499 } else {
500 g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__);
501 }
502}
503
504template <SPColorScalesMode MODE>
506{
507 gfloat rgb[3];
508
509 g_return_if_fail(cmyka != nullptr);
510
511 if constexpr (MODE == SPColorScalesMode::RGB) {
512 SPColor::rgb_to_cmyk_floatv(cmyka, getScaled(_a[0]), getScaled(_a[1]),
513 getScaled(_a[2]));
514 cmyka[4] = getScaled(_a[3]);
515 } else if constexpr (MODE == SPColorScalesMode::HSL) {
516 SPColor::hsl_to_rgb_floatv(rgb, getScaled(_a[0]), getScaled(_a[1]),
517 getScaled(_a[2]));
518 SPColor::rgb_to_cmyk_floatv(cmyka, rgb[0], rgb[1], rgb[2]);
519 cmyka[4] = getScaled(_a[3]);
520 } else if constexpr (MODE == SPColorScalesMode::HSLUV) {
521 SPColor::hsluv_to_rgb_floatv(rgb, getScaled(_a[0]), getScaled(_a[1]),
522 getScaled(_a[2]));
523 SPColor::rgb_to_cmyk_floatv(cmyka, rgb[0], rgb[1], rgb[2]);
524 cmyka[4] = getScaled(_a[3]);
525 } else if constexpr (MODE == SPColorScalesMode::OKLAB) {
526 auto const tmp = Oklab::oklab_to_rgb(
527 Oklab::okhsl_to_oklab({ getScaled(_a[0]),
528 getScaled(_a[1]),
529 getScaled(_a[2]) }));
530 SPColor::rgb_to_cmyk_floatv(cmyka, (float)tmp[0], (float)tmp[1], (float)tmp[2]);
531 cmyka[4] = getScaled(_a[3]);
532 } else if constexpr (MODE == SPColorScalesMode::CMYK) {
533 cmyka[0] = getScaled(_a[0]);
534 cmyka[1] = getScaled(_a[1]);
535 cmyka[2] = getScaled(_a[2]);
536 cmyka[3] = getScaled(_a[3]);
537 cmyka[4] = getScaled(_a[4]);
538 } else {
539 g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__);
540 }
541}
542
543template <SPColorScalesMode MODE>
545{
546 gfloat c[4];
547 guint32 rgba;
548
549 _getRgbaFloatv(c);
550
551 rgba = SP_RGBA32_F_COMPOSE(c[0], c[1], c[2], c[3]);
552
553 return rgba;
554}
555
556template <SPColorScalesMode MODE>
558{
559 gfloat rgba[4];
560 gfloat c[4];
561 int alpha_index = 0;
562
563 if constexpr (MODE == SPColorScalesMode::NONE) {
564 rgba[0] = rgba[1] = rgba[2] = rgba[3] = 1.0;
565 } else {
566 _getRgbaFloatv(rgba);
567 }
568
569 if constexpr (MODE == SPColorScalesMode::RGB) {
570 _setRangeLimit(255.0);
571 _a[3]->set_upper(100.0);
572 _l[0]->set_markup_with_mnemonic(_("_R:"));
573 _s[0]->set_tooltip_text(_("Red"));
574 _b[0]->set_tooltip_text(_("Red"));
575 _l[1]->set_markup_with_mnemonic(_("_G:"));
576 _s[1]->set_tooltip_text(_("Green"));
577 _b[1]->set_tooltip_text(_("Green"));
578 _l[2]->set_markup_with_mnemonic(_("_B:"));
579 _s[2]->set_tooltip_text(_("Blue"));
580 _b[2]->set_tooltip_text(_("Blue"));
581 alpha_index = 3;
582 _l[3]->set_markup_with_mnemonic(_("_A:"));
583 _s[3]->set_tooltip_text(_("Alpha (opacity)"));
584 _b[3]->set_tooltip_text(_("Alpha (opacity)"));
585 _s[0]->setMap(nullptr);
586 _l[4]->set_visible(false);
587 _s[4]->set_visible(false);
588 _b[4]->set_visible(false);
589 _updating = true;
590 setScaled(_a[0], rgba[0]);
591 setScaled(_a[1], rgba[1]);
592 setScaled(_a[2], rgba[2]);
593 setScaled(_a[3], rgba[3]);
594 _updateSliders(CSC_CHANNELS_ALL);
595 _updating = false;
596 } else if constexpr (MODE == SPColorScalesMode::HSL) {
597 _setRangeLimit(100.0);
598
599 _l[0]->set_markup_with_mnemonic(_("_H:"));
600 _s[0]->set_tooltip_text(_("Hue"));
601 _b[0]->set_tooltip_text(_("Hue"));
602 _a[0]->set_upper(360.0);
603
604 _l[1]->set_markup_with_mnemonic(_("_S:"));
605 _s[1]->set_tooltip_text(_("Saturation"));
606 _b[1]->set_tooltip_text(_("Saturation"));
607
608 _l[2]->set_markup_with_mnemonic(_("_L:"));
609 _s[2]->set_tooltip_text(_("Lightness"));
610 _b[2]->set_tooltip_text(_("Lightness"));
611
612 alpha_index = 3;
613 _l[3]->set_markup_with_mnemonic(_("_A:"));
614 _s[3]->set_tooltip_text(_("Alpha (opacity)"));
615 _b[3]->set_tooltip_text(_("Alpha (opacity)"));
616 _s[0]->setMap(sp_color_scales_hue_map());
617 _l[4]->set_visible(false);
618 _s[4]->set_visible(false);
619 _b[4]->set_visible(false);
620 _updating = true;
621 c[0] = 0.0;
622
623 SPColor::rgb_to_hsl_floatv(c, rgba[0], rgba[1], rgba[2]);
624
625 setScaled(_a[0], c[0]);
626 setScaled(_a[1], c[1]);
627 setScaled(_a[2], c[2]);
628 setScaled(_a[3], rgba[3]);
629
630 _updateSliders(CSC_CHANNELS_ALL);
631 _updating = false;
632 } else if constexpr (MODE == SPColorScalesMode::HSV) {
633 _setRangeLimit(100.0);
634
635 _l[0]->set_markup_with_mnemonic(_("_H:"));
636 _s[0]->set_tooltip_text(_("Hue"));
637 _b[0]->set_tooltip_text(_("Hue"));
638 _a[0]->set_upper(360.0);
639
640 _l[1]->set_markup_with_mnemonic(_("_S:"));
641 _s[1]->set_tooltip_text(_("Saturation"));
642 _b[1]->set_tooltip_text(_("Saturation"));
643
644 _l[2]->set_markup_with_mnemonic(_("_V:"));
645 _s[2]->set_tooltip_text(_("Value"));
646 _b[2]->set_tooltip_text(_("Value"));
647
648 alpha_index = 3;
649 _l[3]->set_markup_with_mnemonic(_("_A:"));
650 _s[3]->set_tooltip_text(_("Alpha (opacity)"));
651 _b[3]->set_tooltip_text(_("Alpha (opacity)"));
652 _s[0]->setMap(sp_color_scales_hue_map());
653 _l[4]->set_visible(false);
654 _s[4]->set_visible(false);
655 _b[4]->set_visible(false);
656 _updating = true;
657 c[0] = 0.0;
658
659 SPColor::rgb_to_hsv_floatv(c, rgba[0], rgba[1], rgba[2]);
660
661 setScaled(_a[0], c[0]);
662 setScaled(_a[1], c[1]);
663 setScaled(_a[2], c[2]);
664 setScaled(_a[3], rgba[3]);
665
666 _updateSliders(CSC_CHANNELS_ALL);
667 _updating = false;
668 } else if constexpr (MODE == SPColorScalesMode::CMYK) {
669 _setRangeLimit(100.0);
670 _l[0]->set_markup_with_mnemonic(_("_C:"));
671 _s[0]->set_tooltip_text(_("Cyan"));
672 _b[0]->set_tooltip_text(_("Cyan"));
673
674 _l[1]->set_markup_with_mnemonic(_("_M:"));
675 _s[1]->set_tooltip_text(_("Magenta"));
676 _b[1]->set_tooltip_text(_("Magenta"));
677
678 _l[2]->set_markup_with_mnemonic(_("_Y:"));
679 _s[2]->set_tooltip_text(_("Yellow"));
680 _b[2]->set_tooltip_text(_("Yellow"));
681
682 _l[3]->set_markup_with_mnemonic(_("_K:"));
683 _s[3]->set_tooltip_text(_("Black"));
684 _b[3]->set_tooltip_text(_("Black"));
685
686 alpha_index = 4;
687 _l[4]->set_markup_with_mnemonic(_("_A:"));
688 _s[4]->set_tooltip_text(_("Alpha (opacity)"));
689 _b[4]->set_tooltip_text(_("Alpha (opacity)"));
690
691 _s[0]->setMap(nullptr);
692 _l[4]->set_visible(true);
693 _s[4]->set_visible(true);
694 _b[4]->set_visible(true);
695 _updating = true;
696
697 SPColor::rgb_to_cmyk_floatv(c, rgba[0], rgba[1], rgba[2]);
698 setScaled(_a[0], c[0]);
699 setScaled(_a[1], c[1]);
700 setScaled(_a[2], c[2]);
701 setScaled(_a[3], c[3]);
702
703 setScaled(_a[4], rgba[3]);
704 _updateSliders(CSC_CHANNELS_ALL);
705 _updating = false;
706 } else if constexpr (MODE == SPColorScalesMode::HSLUV) {
707 _setRangeLimit(100.0);
708
709 _l[0]->set_markup_with_mnemonic(_("_H*:"));
710 _s[0]->set_tooltip_text(_("Hue"));
711 _b[0]->set_tooltip_text(_("Hue"));
712 _a[0]->set_upper(360.0);
713
714 _l[1]->set_markup_with_mnemonic(_("_S*:"));
715 _s[1]->set_tooltip_text(_("Saturation"));
716 _b[1]->set_tooltip_text(_("Saturation"));
717
718 _l[2]->set_markup_with_mnemonic(_("_L*:"));
719 _s[2]->set_tooltip_text(_("Lightness"));
720 _b[2]->set_tooltip_text(_("Lightness"));
721
722 alpha_index = 3;
723 _l[3]->set_markup_with_mnemonic(_("_A:"));
724 _s[3]->set_tooltip_text(_("Alpha (opacity)"));
725 _b[3]->set_tooltip_text(_("Alpha (opacity)"));
726
727 _s[0]->setMap(hsluvHueMap(0.0f, 0.0f, &_sliders_maps[0]));
728 _s[1]->setMap(hsluvSaturationMap(0.0f, 0.0f, &_sliders_maps[1]));
729 _s[2]->setMap(hsluvLightnessMap(0.0f, 0.0f, &_sliders_maps[2]));
730
731 _l[4]->set_visible(false);
732 _s[4]->set_visible(false);
733 _b[4]->set_visible(false);
734 _updating = true;
735 c[0] = 0.0;
736
737 SPColor::rgb_to_hsluv_floatv(c, rgba[0], rgba[1], rgba[2]);
738
739 setScaled(_a[0], c[0]);
740 setScaled(_a[1], c[1]);
741 setScaled(_a[2], c[2]);
742 setScaled(_a[3], rgba[3]);
743
744 _updateSliders(CSC_CHANNELS_ALL);
745 _updating = false;
746 } else if constexpr (MODE == SPColorScalesMode::OKLAB) {
747 _setRangeLimit(100.0);
748
749 _l[0]->set_markup_with_mnemonic(_("_H<sub>OK</sub>:"));
750 _s[0]->set_tooltip_text(_("Hue"));
751 _b[0]->set_tooltip_text(_("Hue"));
752 _a[0]->set_upper(360.0);
753
754 _l[1]->set_markup_with_mnemonic(_("_S<sub>OK</sub>:"));
755 _s[1]->set_tooltip_text(_("Saturation"));
756 _b[1]->set_tooltip_text(_("Saturation"));
757
758 _l[2]->set_markup_with_mnemonic(_("_L<sub>OK</sub>:"));
759 _s[2]->set_tooltip_text(_("Lightness"));
760 _b[2]->set_tooltip_text(_("Lightness"));
761
762 alpha_index = 3;
763 _l[3]->set_markup_with_mnemonic(_("_A:"));
764 _s[3]->set_tooltip_text(_("Alpha (opacity)"));
765 _b[3]->set_tooltip_text(_("Alpha (opacity)"));
766
767 _l[4]->set_visible(false);
768 _s[4]->set_visible(false);
769 _b[4]->set_visible(false);
770 _updating = true;
771
772 auto const tmp = Oklab::oklab_to_okhsl(Oklab::rgb_to_oklab({rgba[0], rgba[1], rgba[2]}));
773 for (size_t i : {0, 1, 2}) {
774 setScaled(_a[i], tmp[i]);
775 }
776 setScaled(_a[3], rgba[3]);
777
778 _updateSliders(CSC_CHANNELS_ALL);
779 _updating = false;
780 } else {
781 g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__);
782 }
783
784 if (no_alpha && alpha_index > 0) {
785 _l[alpha_index]->set_visible(false);
786 _s[alpha_index]->set_visible(false);
787 _b[alpha_index]->set_visible(false);
788 }
789}
790
791template <SPColorScalesMode MODE>
793
794template <SPColorScalesMode MODE>
796{
797 if (_updating) { return; }
798
799 if (!_dragging) {
800 _dragging = true;
801 _color.setHeld(true);
802 }
803}
804
805template <SPColorScalesMode MODE>
807{
808 if (_updating) { return; }
809
810 if (_dragging) {
811 _dragging = false;
812 _color.setHeld(false);
813 }
814}
815
816template <SPColorScalesMode MODE>
818{
819 if (_updating) { return; }
820
821 _recalcColor();
822}
823
824template <SPColorScalesMode MODE>
826{
827 if (_updating) { return; }
828
829 _updateSliders((1 << channel));
830 _recalcColor();
831}
832
833template <SPColorScalesMode MODE>
835{
836 if constexpr (
840 {
841 return;
842 }
843
844 if (_updating) { return; }
845
846 _updating = true;
847
848 double rgb[3];
849 _wheel->getRgbV(rgb);
850 SPColor color(rgb[0], rgb[1], rgb[2]);
851
852 _color_changed.block();
853 _color_dragged.block();
854
855 // Color
856 _color.setHeld(_wheel->isAdjusting());
857 _color.setColor(color);
858
859 // Sliders
860 _updateDisplay(false);
861
862 _color_changed.unblock();
863 _color_dragged.unblock();
864
865 _updating = false;
866}
867
868template <SPColorScalesMode MODE>
870{
871 gfloat rgb0[3], rgbm[3], rgb1[3];
872
873#ifdef SPCS_PREVIEW
874 guint32 rgba;
875#endif
876
877 std::array<double, 4> const adj = [this]() -> std::array<double, 4> {
878 if constexpr (MODE == SPColorScalesMode::CMYK) {
879 return { getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), getScaled(_a[3]) };
880 } else {
881 return { getScaled(_a[0]), getScaled(_a[1]), getScaled(_a[2]), 0.0 };
882 }
883 }();
884
885 if constexpr (MODE == SPColorScalesMode::RGB) {
886 if ((channels != CSC_CHANNEL_R) && (channels != CSC_CHANNEL_A)) {
887 /* Update red */
888 _s[0]->setColors(SP_RGBA32_F_COMPOSE(0.0, adj[1], adj[2], 1.0),
889 SP_RGBA32_F_COMPOSE(0.5, adj[1], adj[2], 1.0),
890 SP_RGBA32_F_COMPOSE(1.0, adj[1], adj[2], 1.0));
891 }
892 if ((channels != CSC_CHANNEL_G) && (channels != CSC_CHANNEL_A)) {
893 /* Update green */
894 _s[1]->setColors(SP_RGBA32_F_COMPOSE(adj[0], 0.0, adj[2], 1.0),
895 SP_RGBA32_F_COMPOSE(adj[0], 0.5, adj[2], 1.0),
896 SP_RGBA32_F_COMPOSE(adj[0], 1.0, adj[2], 1.0));
897 }
898 if ((channels != CSC_CHANNEL_B) && (channels != CSC_CHANNEL_A)) {
899 /* Update blue */
900 _s[2]->setColors(SP_RGBA32_F_COMPOSE(adj[0], adj[1], 0.0, 1.0),
901 SP_RGBA32_F_COMPOSE(adj[0], adj[1], 0.5, 1.0),
902 SP_RGBA32_F_COMPOSE(adj[0], adj[1], 1.0, 1.0));
903 }
904 if (channels != CSC_CHANNEL_A) {
905 /* Update alpha */
906 _s[3]->setColors(SP_RGBA32_F_COMPOSE(adj[0], adj[1], adj[2], 0.0),
907 SP_RGBA32_F_COMPOSE(adj[0], adj[1], adj[2], 0.5),
908 SP_RGBA32_F_COMPOSE(adj[0], adj[1], adj[2], 1.0));
909 }
910 } else if constexpr (MODE == SPColorScalesMode::HSL) {
911 /* Hue is never updated */
912 if ((channels != CSC_CHANNEL_S) && (channels != CSC_CHANNEL_A)) {
913 /* Update saturation */
914 SPColor::hsl_to_rgb_floatv(rgb0, adj[0], 0.0, adj[2]);
915 SPColor::hsl_to_rgb_floatv(rgbm, adj[0], 0.5, adj[2]);
916 SPColor::hsl_to_rgb_floatv(rgb1, adj[0], 1.0, adj[2]);
917 _s[1]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
918 SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
919 SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
920 }
921 if ((channels != CSC_CHANNEL_V) && (channels != CSC_CHANNEL_A)) {
922 /* Update value */
923 SPColor::hsl_to_rgb_floatv(rgb0, adj[0], adj[1], 0.0);
924 SPColor::hsl_to_rgb_floatv(rgbm, adj[0], adj[1], 0.5);
925 SPColor::hsl_to_rgb_floatv(rgb1, adj[0], adj[1], 1.0);
926 _s[2]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
927 SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
928 SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
929 }
930 if (channels != CSC_CHANNEL_A) {
931 /* Update alpha */
932 SPColor::hsl_to_rgb_floatv(rgb0, adj[0], adj[1], adj[2]);
933 _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.0),
934 SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.5),
935 SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0));
936 }
937 } else if constexpr (MODE == SPColorScalesMode::HSV) {
938 /* Hue is never updated */
939 if ((channels != CSC_CHANNEL_S) && (channels != CSC_CHANNEL_A)) {
940 /* Update saturation */
941 SPColor::hsv_to_rgb_floatv(rgb0, adj[0], 0.0, adj[2]);
942 SPColor::hsv_to_rgb_floatv(rgbm, adj[0], 0.5, adj[2]);
943 SPColor::hsv_to_rgb_floatv(rgb1, adj[0], 1.0, adj[2]);
944 _s[1]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
945 SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
946 SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
947 }
948 if ((channels != CSC_CHANNEL_V) && (channels != CSC_CHANNEL_A)) {
949 /* Update value */
950 SPColor::hsv_to_rgb_floatv(rgb0, adj[0], adj[1], 0.0);
951 SPColor::hsv_to_rgb_floatv(rgbm, adj[0], adj[1], 0.5);
952 SPColor::hsv_to_rgb_floatv(rgb1, adj[0], adj[1], 1.0);
953 _s[2]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
954 SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
955 SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
956 }
957 if (channels != CSC_CHANNEL_A) {
958 /* Update alpha */
959 SPColor::hsv_to_rgb_floatv(rgb0, adj[0], adj[1], adj[2]);
960 _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.0),
961 SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.5),
962 SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0));
963 }
964 } else if constexpr (MODE == SPColorScalesMode::CMYK) {
965 if ((channels != CSC_CHANNEL_C) && (channels != CSC_CHANNEL_CMYKA)) {
966 /* Update C */
967 SPColor::cmyk_to_rgb_floatv(rgb0, 0.0, adj[1], adj[2], adj[3]);
968 SPColor::cmyk_to_rgb_floatv(rgbm, 0.5, adj[1], adj[2], adj[3]);
969 SPColor::cmyk_to_rgb_floatv(rgb1, 1.0, adj[1], adj[2], adj[3]);
970 _s[0]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
971 SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
972 SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
973 }
974 if ((channels != CSC_CHANNEL_M) && (channels != CSC_CHANNEL_CMYKA)) {
975 /* Update M */
976 SPColor::cmyk_to_rgb_floatv(rgb0, adj[0], 0.0, adj[2], adj[3]);
977 SPColor::cmyk_to_rgb_floatv(rgbm, adj[0], 0.5, adj[2], adj[3]);
978 SPColor::cmyk_to_rgb_floatv(rgb1, adj[0], 1.0, adj[2], adj[3]);
979 _s[1]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
980 SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
981 SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
982 }
983 if ((channels != CSC_CHANNEL_Y) && (channels != CSC_CHANNEL_CMYKA)) {
984 /* Update Y */
985 SPColor::cmyk_to_rgb_floatv(rgb0, adj[0], adj[1], 0.0, adj[3]);
986 SPColor::cmyk_to_rgb_floatv(rgbm, adj[0], adj[1], 0.5, adj[3]);
987 SPColor::cmyk_to_rgb_floatv(rgb1, adj[0], adj[1], 1.0, adj[3]);
988 _s[2]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
989 SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
990 SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
991 }
992 if ((channels != CSC_CHANNEL_K) && (channels != CSC_CHANNEL_CMYKA)) {
993 /* Update K */
994 SPColor::cmyk_to_rgb_floatv(rgb0, adj[0], adj[1], adj[2], 0.0);
995 SPColor::cmyk_to_rgb_floatv(rgbm, adj[0], adj[1], adj[2], 0.5);
996 SPColor::cmyk_to_rgb_floatv(rgb1, adj[0], adj[1], adj[2], 1.0);
997 _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0),
998 SP_RGBA32_F_COMPOSE(rgbm[0], rgbm[1], rgbm[2], 1.0),
999 SP_RGBA32_F_COMPOSE(rgb1[0], rgb1[1], rgb1[2], 1.0));
1000 }
1001 if (channels != CSC_CHANNEL_CMYKA) {
1002 /* Update alpha */
1003 SPColor::cmyk_to_rgb_floatv(rgb0, adj[0], adj[1], adj[2], adj[3]);
1004 _s[4]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.0),
1005 SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.5),
1006 SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0));
1007 }
1008 } else if constexpr (MODE == SPColorScalesMode::HSLUV) {
1009 if ((channels != CSC_CHANNEL_H) && (channels != CSC_CHANNEL_A)) {
1010 /* Update hue */
1011 _s[0]->setMap(hsluvHueMap(adj[1], adj[2], &_sliders_maps[0]));
1012 }
1013 if ((channels != CSC_CHANNEL_S) && (channels != CSC_CHANNEL_A)) {
1014 /* Update saturation (scaled chroma) */
1015 _s[1]->setMap(hsluvSaturationMap(adj[0], adj[2], &_sliders_maps[1]));
1016 }
1017 if ((channels != CSC_CHANNEL_V) && (channels != CSC_CHANNEL_A)) {
1018 /* Update lightness */
1019 _s[2]->setMap(hsluvLightnessMap(adj[0], adj[1], &_sliders_maps[2]));
1020 }
1021 if (channels != CSC_CHANNEL_A) {
1022 /* Update alpha */
1023 SPColor::hsluv_to_rgb_floatv(rgb0, adj[0], adj[1], adj[2]);
1024 _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.0),
1025 SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 0.5),
1026 SP_RGBA32_F_COMPOSE(rgb0[0], rgb0[1], rgb0[2], 1.0));
1027 }
1028 } else if constexpr (MODE == SPColorScalesMode::OKLAB) {
1029 if (channels != CSC_CHANNEL_H && channels != CSC_CHANNEL_A) {
1030 _s[0]->setMap(Oklab::render_hue_scale(adj[1], adj[2], &_sliders_maps[0]));
1031 }
1032 if ((channels != CSC_CHANNEL_S) && (channels != CSC_CHANNEL_A)) {
1033 _s[1]->setMap(Oklab::render_saturation_scale(360.0 * adj[0], adj[2], &_sliders_maps[1]));
1034 }
1035 if ((channels != CSC_CHANNEL_V) && (channels != CSC_CHANNEL_A)) {
1036 _s[2]->setMap(Oklab::render_lightness_scale(360.0 * adj[0], adj[1], &_sliders_maps[2]));
1037 }
1038 if (channels != CSC_CHANNEL_A) { // Update the alpha gradient.
1039 auto const rgb = Oklab::oklab_to_rgb(
1040 Oklab::okhsl_to_oklab({ getScaled(_a[0]),
1041 getScaled(_a[1]),
1042 getScaled(_a[2]) }));
1043 _s[3]->setColors(SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 0.0),
1044 SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 0.5),
1045 SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 1.0));
1046 }
1047 } else {
1048 g_warning("file %s: line %d: Illegal color selector mode", __FILE__, __LINE__);
1049 }
1050
1051#ifdef SPCS_PREVIEW
1052 rgba = sp_color_scales_get_rgba32(cs);
1053 sp_color_preview_set_rgba32(SP_COLOR_PREVIEW(_p), rgba);
1054#endif
1055}
1056
1057static guchar const *sp_color_scales_hue_map()
1058{
1059 static std::array<guchar, 4 * 1024> const map = []() {
1060 std::array<guchar, 4 * 1024> m;
1061
1062 guchar *p;
1063 p = m.data();
1064 for (gint h = 0; h < 1024; h++) {
1065 gfloat rgb[3];
1066 SPColor::hsl_to_rgb_floatv(rgb, h / 1024.0, 1.0, 0.5);
1067 *p++ = SP_COLOR_F_TO_U(rgb[0]);
1068 *p++ = SP_COLOR_F_TO_U(rgb[1]);
1069 *p++ = SP_COLOR_F_TO_U(rgb[2]);
1070 *p++ = 0xFF;
1071 }
1072
1073 return m;
1074 }();
1075
1076 return map.data();
1077}
1078
1079static void sp_color_interp(guchar *out, gint steps, gfloat *start, gfloat *end)
1080{
1081 gfloat s[3] = {
1082 (end[0] - start[0]) / steps,
1083 (end[1] - start[1]) / steps,
1084 (end[2] - start[2]) / steps
1085 };
1086
1087 guchar *p = out;
1088 for (int i = 0; i < steps; i++) {
1089 *p++ = SP_COLOR_F_TO_U(start[0] + s[0] * i);
1090 *p++ = SP_COLOR_F_TO_U(start[1] + s[1] * i);
1091 *p++ = SP_COLOR_F_TO_U(start[2] + s[2] * i);
1092 *p++ = 0xFF;
1093 }
1094}
1095
1096// TODO: consider turning this into a generator (without memory allocation).
1097template <typename T>
1098static std::vector<T> range (int const steps, T start, T end)
1099{
1100 T step = (end - start) / (steps - 1);
1101
1102 std::vector<T> out;
1103 out.reserve(steps);
1104
1105 for (int i = 0; i < steps-1; i++) {
1106 out.emplace_back(start + step * i);
1107 }
1108 out.emplace_back(end);
1109
1110 return out;
1111}
1112
1113static guchar const *sp_color_scales_hsluv_map(guchar *map,
1114 std::function<void(float*, float)> callback)
1115{
1116 // Only generate 21 colors and interpolate between them to get 1024
1117 constexpr static int STEPS = 21;
1118 constexpr static int COLORS = (STEPS+1) * 3;
1119 static auto const steps = range<float>(STEPS+1, 0.f, 1.f);
1120
1121 // Generate color steps
1122 gfloat colors[COLORS];
1123 for (int i = 0; i < STEPS+1; i++) {
1124 callback(colors+(i*3), steps[i]);
1125 }
1126
1127 for (int i = 0; i < STEPS; i++) {
1128 int a = steps[i] * 1023,
1129 b = steps[i+1] * 1023;
1130 sp_color_interp(map+(a * 4), b-a, colors+(i*3), colors+((i+1)*3));
1131 }
1132
1133 return map;
1134}
1135
1136template <SPColorScalesMode MODE>
1137guchar const *ColorScales<MODE>::hsluvHueMap(gfloat s, gfloat l,
1138 std::array<guchar, 4 * 1024> *map)
1139{
1140 return sp_color_scales_hsluv_map(map->data(), [s, l] (float *colors, float h) {
1141 SPColor::hsluv_to_rgb_floatv(colors, h, s, l);
1142 });
1143}
1144
1145template <SPColorScalesMode MODE>
1146guchar const *ColorScales<MODE>::hsluvSaturationMap(gfloat h, gfloat l,
1147 std::array<guchar, 4 * 1024> *map)
1148{
1149 return sp_color_scales_hsluv_map(map->data(), [h, l] (float *colors, float s) {
1150 SPColor::hsluv_to_rgb_floatv(colors, h, s, l);
1151 });
1152}
1153
1154template <SPColorScalesMode MODE>
1155guchar const *ColorScales<MODE>::hsluvLightnessMap(gfloat h, gfloat s,
1156 std::array<guchar, 4 * 1024> *map)
1157{
1158 return sp_color_scales_hsluv_map(map->data(), [h, s] (float *colors, float l) {
1159 SPColor::hsluv_to_rgb_floatv(colors, h, s, l);
1160 });
1161}
1162
1163template <SPColorScalesMode MODE>
1165{}
1166
1167template <SPColorScalesMode MODE>
1169{
1170 Gtk::Widget *w = Gtk::make_managed<ColorScales<MODE>>(color, no_alpha);
1171 return w;
1172}
1173
1174template <SPColorScalesMode MODE>
1176{
1177 if constexpr (MODE == SPColorScalesMode::RGB) {
1178 return gettext(ColorScales<>::SUBMODE_NAMES[1]);
1179 } else if constexpr (MODE == SPColorScalesMode::HSL) {
1180 return gettext(ColorScales<>::SUBMODE_NAMES[2]);
1181 } else if constexpr (MODE == SPColorScalesMode::CMYK) {
1182 return gettext(ColorScales<>::SUBMODE_NAMES[3]);
1183 } else if constexpr (MODE == SPColorScalesMode::HSV) {
1184 return gettext(ColorScales<>::SUBMODE_NAMES[4]);
1185 } else if constexpr (MODE == SPColorScalesMode::HSLUV) {
1186 return gettext(ColorScales<>::SUBMODE_NAMES[5]);
1187 } else if constexpr (MODE == SPColorScalesMode::OKLAB) {
1188 return gettext(ColorScales<>::SUBMODE_NAMES[6]);
1189 } else {
1190 return gettext(ColorScales<>::SUBMODE_NAMES[0]);
1191 }
1192}
1193
1194// Explicit instantiations
1202
1210
1211} // namespace Inkscape::UI::Widget
1212
1213/*
1214 Local Variables:
1215 mode:c++
1216 c-file-style:"stroustrup"
1217 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1218 indent-tabs-mode:nil
1219 fill-column:99
1220 End:
1221*/
1222// vim:filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8: textwidth=99:
Geom::IntRect visible
Definition: canvas.cpp:154
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
Definition: preferences.h:363
static Preferences * get()
Access the singleton Preferences object.
Definition: preferences.h:599
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
sigc::signal< void()> signal_changed
sigc::signal< void()> signal_dragged
Gtk::Widget * createWidget(Inkscape::UI::SelectedColor &color, bool no_alpha) const override
Glib::ustring modeName() const override
static guchar const * hsluvSaturationMap(gfloat h, gfloat l, std::array< guchar, 4 *1024 > *map)
static double getScaled(Glib::RefPtr< Gtk::Adjustment > const &a)
Inkscape::UI::Widget::ColorSlider * _s[5]
Definition: color-scales.h:94
static guchar const * hsluvLightnessMap(gfloat h, gfloat s, std::array< guchar, 4 *1024 > *map)
SPColorScalesMode getMode() const
void _updateSliders(guint channels)
static void setScaled(Glib::RefPtr< Gtk::Adjustment > &a, double v, bool constrained=false)
ColorScales(SelectedColor &color, bool no_alpha)
void _updateDisplay(bool update_wheel=true)
void _setRangeLimit(gdouble upper)
static gchar const * SUBMODE_NAMES[]
Definition: color-scales.h:51
static guchar const * hsluvHueMap(gfloat s, gfloat l, std::array< guchar, 4 *1024 > *map)
void _getCmykaFloatv(gfloat *cmyka)
An RGB color with optional icc-color part.
Definition: color.h:51
void set(float r, float g, float b)
Sets RGB values and colorspace in color.
Definition: color.cpp:116
void get_rgb_floatv(float *rgb) const
Fill rgb float array with values from SPColor.
Definition: color.cpp:299
static void rgb_to_hsv_floatv(float *hsv, float r, float g, float b)
Fill hsv float array from r,g,b float values.
Definition: color.cpp:335
static void cmyk_to_rgb_floatv(float *rgb, float c, float m, float y, float k)
Fill rgb float array from c,m,y,k float values.
Definition: color.cpp:513
static void hsluv_to_rgb_floatv(float *rgb, float h, float s, float l)
Fill rgb float array from h,s,l float values.
Definition: color.cpp:551
static void rgb_to_hsluv_floatv(float *hsluv, float r, float g, float b)
Fill hsluv float array from r,g,b float values.
Definition: color.cpp:536
static void hsl_to_rgb_floatv(float *rgb, float h, float s, float l)
Fill rgb float array from h,s,l float values.
Definition: color.cpp:457
static void hsv_to_rgb_floatv(float *rgb, float h, float s, float v)
Fill rgb float array from h,s,v float values.
Definition: color.cpp:372
static void rgb_to_cmyk_floatv(float *cmyk, float r, float g, float b)
Fill cmyk float array from r,g,b float values.
Definition: color.cpp:482
static void rgb_to_hsl_floatv(float *hsl, float r, float g, float b)
Fill hsl float array from r,g,b float values.
Definition: color.cpp:413
TODO: insert short description here.
static constexpr int XPAD
static constexpr int YPAD
static constexpr int CSC_CHANNEL_G
static constexpr int CSC_CHANNEL_CMYKA
static constexpr int CSC_CHANNEL_Y
static constexpr int CSC_CHANNEL_M
static constexpr int CSC_CHANNEL_R
static constexpr int CSC_CHANNEL_A
static constexpr int CSC_CHANNEL_K
static constexpr int CSC_CHANNEL_H
static constexpr int CSC_CHANNEL_C
static constexpr int CSC_CHANNELS_ALL
static constexpr int CSC_CHANNEL_V
static constexpr int CSC_CHANNEL_S
static constexpr int CSC_CHANNEL_B
Color selector using sliders for each components, for multiple color modes.
A slider with colored background - implementation.
unsigned int guint32
Definition: color.h:22
const double w
Definition: conic-4.cpp:19
double c[8][4]
Definition: cylinder3d.cpp:40
void sp_dialog_defocus_on_enter(Gtk::Entry *e)
Event handler for dialog windows.
@ FILL
Gtk::Image * sp_get_icon_image(Glib::ustring const &icon_name, int size)
Definition: icon-loader.cpp:27
Icon Loader.
Geom::Point start
Geom::Point end
SymmetricMatrix< N > adj(const ConstBaseSymmetricMatrix< N > &S)
size_t v
Definition: multi-index.h:105
Definition: desktop.h:51
static const Triplet m[3]
Definition: hsluv.cpp:44
Custom widgets.
Definition: desktop.h:127
const char * get_color_mode_label(SPColorScalesMode mode)
const char * color_mode_name[]
static std::vector< T > range(int const steps, T start, T end)
static const char * color_mode_icons[]
static void sp_color_interp(guchar *out, gint steps, gfloat *start, gfloat *end)
const char * get_color_mode_icon(SPColorScalesMode mode)
static guchar const * sp_color_scales_hue_map()
std::unique_ptr< Inkscape::UI::ColorSelectorFactory > get_factory(SPColorScalesMode mode)
std::vector< ColorPickerDescription > get_color_pickers()
static guchar const * sp_color_scales_hsluv_map(guchar *map, std::function< void(float *, float)> callback)
void pack_start(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the start of box.
Definition: pack.cpp:141
ptr_shared format(char const *format,...) G_GNUC_PRINTF(1
Definition: format.h:33
static void append(std::vector< T > &target, std::vector< T > &&source)
Definition: shortcuts.cpp:515
uint8_t const * render_saturation_scale(double h, double l, std::array< uint8_t, 4 *1024 > *map)
Definition: oklab.cpp:395
Triplet okhsl_to_oklab(Triplet const &ok_hsl_color)
Convert an OKHSL color to the OKLab coordinates.
Definition: oklab.cpp:152
Triplet oklab_to_okhsl(Triplet const &ok_lab_color)
Convert an OKLab color to an OKHSL representation.
Definition: oklab.cpp:126
uint8_t const * render_hue_scale(double s, double l, std::array< uint8_t, 4 *1024 > *map)
Helper functions for rendering color strips used in color sliders.
Definition: oklab.cpp:364
Triplet oklab_to_rgb(Triplet const &oklab_color)
Convert an OKLab color to a gamma-compressed sRGB color.
Definition: oklab.h:40
Triplet rgb_to_oklab(Triplet const &rgb_color)
Convert a gamma-compressed sRGB color to an OKLab color.
Definition: oklab.h:50
uint8_t const * render_lightness_scale(double h, double s, std::array< uint8_t, 4 *1024 > *map)
Definition: oklab.cpp:423
@ HORIZONTAL
The x-dimension (0).
Definition: rectangle.h:43
@ VERTICAL
The y-dimension (1).
Definition: rectangle.h:47
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
int mode
Definition: parametrics.cpp:20
Singleton class to access the preferences file in a convenient way.
RGB rgb
Definition: quantize.cpp:36
Color selected in color selector widget.
std::unique_ptr< Toolbar >(* create)(SPDesktop *desktop)
Definition: toolbars.cpp:63