Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
ink-color-wheel.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
7 * Authors:
8 * Tavmjong Bah
9 * Massinissa Derriche <massinissa.derriche@gmail.com>
10 *
11 * Copyright (C) 2018, 2021 Authors
12 *
13 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14 */
15
17
18#include <2geom/angle.h>
19#include <2geom/coord.h>
20#include <algorithm>
21#include <cstring>
22#include <gdkmm/display.h>
23#include <gdkmm/general.h>
24#include <gtkmm/drawingarea.h>
25#include <gtkmm/eventcontrollerkey.h>
26#include <gtkmm/eventcontrollermotion.h>
27#include <gtkmm/gestureclick.h>
28#include <sigc++/functors/mem_fun.h>
29#include <utility>
30
31#include "colors/spaces/enum.h"
32#include "colors/spaces/hsluv.h"
33#include "colors/utils.h"
34#include "ui/controller.h"
36#include "ui/util.h"
37#include "ui/widget/bin.h"
38
39using namespace Inkscape::Colors;
42
43namespace Inkscape::UI::Widget {
44
45// Sizes in pixels
46constexpr static int const SIZE = 400;
47constexpr static int const OUTER_CIRCLE_RADIUS = 190;
48constexpr static double MAX_HUE = 360.0;
49constexpr static double MAX_SATURATION = 100.0;
50constexpr static double MAX_LIGHTNESS = 100.0;
51constexpr static double MIN_HUE = 0.0;
52constexpr static double MIN_SATURATION = 0.0;
53constexpr static double MIN_LIGHTNESS = 0.0;
54constexpr static double OUTER_CIRCLE_DASH_SIZE = 10.0;
55constexpr static double VERTEX_EPSILON = 0.01;
56constexpr static double marker_radius = 4.0;
57constexpr static double focus_line_width = 1.0;
58constexpr static double focus_padding = 3.0;
59static auto const focus_dash = std::vector{1.5};
60
62struct Intersection final
63{
64 Intersection();
65
66 Intersection(int line_1, int line_2, Geom::Point &&intersection_point, Geom::Angle start_angle)
67 : line1{line_1}
68 , line2{line_2}
69 , point{intersection_point}
70 , polar_angle{point}
71 , relative_angle{polar_angle - start_angle}
72 {
73 }
74
75 int line1 = 0;
76 int line2 = 0;
77 Geom::Point point;
78 Geom::Angle polar_angle = 0.0;
83 Geom::Angle relative_angle = 0.0;
84};
85
86static double lerp(double v0, double v1, double t0, double t1, double t);
87static ColorPoint lerp(ColorPoint const &v0, ColorPoint const &v1, double t0, double t1, double t);
88static double luminance(Color const &color);
89static Geom::Point to_pixel_coordinate(Geom::Point const &point, double scale, double resize);
90static Geom::Point from_pixel_coordinate(Geom::Point const &point, double scale, double resize);
91static std::vector<Geom::Point> to_pixel_coordinate(std::vector<Geom::Point> const &points, double scale,
92 double resize);
93static void draw_vertical_padding(ColorPoint p0, ColorPoint p1, int padding, bool pad_upwards, guint32 *buffer,
94 int height, int stride);
95
96/* Base Color Wheel */
97
98ColorWheelBase::ColorWheelBase(Type type, std::vector<double> initial_color)
99 : Gtk::AspectFrame(0.5, 0.5, 1.0, false)
100 , _bin{Gtk::make_managed<UI::Widget::Bin>()}
101 , _values{type, std::move(initial_color)}
102 , _drawing_area{Gtk::make_managed<Gtk::DrawingArea>()} {
103 construct();
104}
105
107 set_name("ColorWheel");
108 add_css_class("flat");
109
110 _drawing_area->set_focusable(true);
111 _drawing_area->set_expand(true);
113 _drawing_area->set_draw_func(sigc::mem_fun(*this, &ColorWheelBase::on_drawing_area_draw ));
114 _drawing_area->property_has_focus().signal_changed().connect([this]{ _drawing_area->queue_draw(); });
116 set_child(*_bin);
117
118 auto const click = Gtk::GestureClick::create();
119 click->set_button(0); // any
120 click->signal_pressed().connect(Controller::use_state([this](auto &&...args) { return on_click_pressed(args...); }, *click));
121 click->signal_released().connect(Controller::use_state([this](auto &, auto &&...args) { return on_click_released(args...); }, *click));
122 _drawing_area->add_controller(click);
123
124 auto const motion = Gtk::EventControllerMotion::create();
125 motion->signal_motion().connect([this, &motion = *motion](auto &&...args) { return _on_motion(motion, args...); });
126 _drawing_area->add_controller(motion);
127
128 auto const key = Gtk::EventControllerKey::create();
129 key->signal_key_pressed().connect(sigc::mem_fun(*this, &ColorWheelBase::on_key_pressed), true);
130 key->signal_key_released().connect(sigc::mem_fun(*this, &ColorWheelBase::on_key_released));
131 _drawing_area->add_controller(key);
132}
133
134ColorWheelBase::ColorWheelBase(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder, Type type, std::vector<double> initial_color):
135 Gtk::AspectFrame(cobject),
136 _bin(Gtk::make_managed<Bin>()),
137 _values(type, initial_color),
138 _drawing_area{Gtk::make_managed<Gtk::DrawingArea>()} {
139
140 construct();
141}
142
143void ColorWheelBase::_on_motion(Gtk::EventControllerMotion const &motion, double x, double y) {
144 if (!_adjusting) return;
145
146 auto state = motion.get_current_event_state();
147 if (!Controller::has_flag(state, Gdk::ModifierType::BUTTON1_MASK)) {
148 // lost button release event
149 on_click_released(0, x, y);
150 return;
151 }
152
153 on_motion(motion, x, y);
154}
155
156sigc::connection ColorWheelBase::connect_color_changed(sigc::slot<void ()> slot)
157{
158 return _signal_color_changed.connect(std::move(slot));
159}
160
162{
164 _drawing_area->queue_draw();
165}
166
168{
169 _drawing_area->queue_draw();
170}
171
173{
174 return _drawing_area->get_allocation();
175}
176
178{
179 return _drawing_area->has_focus();
180}
181
183{
184 _drawing_area->grab_focus();
185}
186
187void ColorWheelBase::on_key_released(unsigned keyval, unsigned /*keycode*/, Gdk::ModifierType state)
188{
189 switch (keyval) {
190 case GDK_KEY_Up:
191 case GDK_KEY_KP_Up:
192 case GDK_KEY_Down:
193 case GDK_KEY_KP_Down:
194 case GDK_KEY_Left:
195 case GDK_KEY_KP_Left:
196 case GDK_KEY_Right:
197 case GDK_KEY_KP_Right:
198 _adjusting = false;
199 }
200}
201
202/* HSL Color Wheel */
203
205 bool const /*overrideHue*/, bool const emit)
206{
207 if (_values.set(color, true)) {
208 _triangle_corners.reset();
209 _marker_point.reset();
210 if (emit)
212 else
214 return true;
215 }
216 return false;
217}
218
220{
221 if (_radii && _source_ring) return;
222
223 auto const [width, height] = *_cache_size;
224 auto const cx = width / 2.0;
225 auto const cy = height / 2.0;
226
227 auto const stride = Cairo::ImageSurface::format_stride_for_width(Cairo::Surface::Format::RGB24, width);
228 _source_ring.reset();
229 _buffer_ring.resize(height * stride / 4);
230
231 auto const &[r_min, r_max] = get_radii();
232 double r2_max = (r_max+2) * (r_max+2); // Must expand a bit to avoid edge effects.
233 double r2_min = (r_min-2) * (r_min-2); // Must shrink a bit to avoid edge effects.
234
235 for (int i = 0; i < height; ++i) {
236 auto p = _buffer_ring.data() + i * width;
237 double dy = (cy - i);
238
239 for (int j = 0; j < width; ++j) {
240 double dx = (j - cx);
241 double r2 = dx * dx + dy * dy;
242 if (r2 < r2_min || r2 > r2_max) {
243 *p++ = 0; // Save calculation time.
244 } else {
245 double angle = atan2 (dy, dx);
246 if (angle < 0.0) {
247 angle += 2.0 * M_PI;
248 }
249 double hue = angle/(2.0 * M_PI);
250 *p++ = Color(Type::HSV, {hue, 1.0, 1.0}).toARGB();
251 }
252 }
253 }
254
255 auto const data = reinterpret_cast<unsigned char *>(_buffer_ring.data());
256 _source_ring = Cairo::ImageSurface::create(data,
257 Cairo::Surface::Format::RGB24,
259}
260
263{
264 bool const source_is_stale = !_triangle_corners.has_value();
265
266 // Reorder so we paint from top down.
267 auto ps = get_triangle_corners();
268 std::sort(ps.begin(), ps.end(), [](auto const &l, auto const &r){ return l.y < r.y; });
269 auto const &[p0, p1, p2] = ps;
270
271 if (_source_triangle && !source_is_stale) return {p0, p1, p2};
272
273 /* The triangle is painted by first finding color points on the
274 * edges of the triangle at the same y value via linearly
275 * interpolating between corner values, and then interpolating along
276 * x between the those edge points. The interpolation is in sRGB
277 * space which leads to a complicated mapping between x/y and
278 * saturation/value. This was probably done to remove the need to
279 * convert between HSV and RGB for each pixel.
280 * Black corner: v = 0, s = 1
281 * White corner: v = 1, s = 0
282 * Color corner; v = 1, s = 1
283 */
284 constexpr int padding = 3; // Avoid edge artifacts.
285
286 _source_triangle.reset();
287 auto const [width, height] = *_cache_size;
288 auto const stride = Cairo::ImageSurface::format_stride_for_width(Cairo::Surface::Format::RGB24, width);
289 _buffer_triangle.resize(height * stride / 4);
290
291 for (int y = 0; y < height; ++y) {
292 if (p0.y <= y + padding && y - padding < p2.y) {
293 // Get values on side at position y.
294 ColorPoint side0;
295 double y_inter = std::clamp(static_cast<double>(y), p0.y, p2.y);
296 if (y < p1.y) {
297 side0 = lerp(p0, p1, p0.y, p1.y, y_inter);
298 } else {
299 side0 = lerp(p1, p2, p1.y, p2.y, y_inter);
300 }
301
302 ColorPoint side1 = lerp(p0, p2, p0.y, p2.y, y_inter);
303
304 // side0 should be on left
305 if (side0.x > side1.x) {
306 std::swap (side0, side1);
307 }
308
309 int const x_start = std::max(0, static_cast<int>(side0.x));
310 int const x_end = std::min(static_cast<int>(side1.x), width);
311
312 auto p = _buffer_triangle.data() + y * (stride / 4);
313 int x = 0;
314 for (; x <= x_start; ++x) {
315 *p++ = side0.color.toARGB();
316 }
317 for (; x < x_end; ++x) {
318 *p++ = lerp(side0, side1, side0.x, side1.x, x).color.toARGB();
319 }
320 for (; x < width; ++x) {
321 *p++ = side1.color.toARGB();
322 }
323 }
324 }
325
326 // add vertical padding to each side separately
327
328 ColorPoint temp_point = lerp(p0, p1, p0.x, p1.x, (p0.x + p1.x) / 2.0);
329 bool pad_upwards = _is_in_triangle(temp_point.x, temp_point.y + 1);
330 draw_vertical_padding(p0, p1, padding, pad_upwards, _buffer_triangle.data(), height, stride / 4);
331
332 temp_point = lerp(p0, p2, p0.x, p2.x, (p0.x + p2.x) / 2.0);
333 pad_upwards = _is_in_triangle(temp_point.x, temp_point.y + 1);
334 draw_vertical_padding(p0, p2, padding, pad_upwards, _buffer_triangle.data(), height, stride / 4);
335
336 temp_point = lerp(p1, p2, p1.x, p2.x, (p1.x + p2.x) / 2.0);
337 pad_upwards = _is_in_triangle(temp_point.x, temp_point.y + 1);
338 draw_vertical_padding(p1, p2, padding, pad_upwards, _buffer_triangle.data(), height, stride / 4);
339
340 auto const data = reinterpret_cast<unsigned char *>(_buffer_triangle.data());
341 _source_triangle = Cairo::ImageSurface::create(data,
342 Cairo::Surface::Format::RGB24,
344
345 return {p0, p1, p2};
346}
347
349{
350 auto const size = Geom::IntPoint{width, height};
351 if (size == _cache_size) return;
353 _radii.reset();
354 _source_ring.reset();
355}
356
357void ColorWheelHSL::on_drawing_area_draw(Cairo::RefPtr<Cairo::Context> const &cr, int, int)
358{
359 auto const [width, height] = *_cache_size;
360 auto const cx = width / 2.0;
361 auto const cy = height / 2.0;
362
363 cr->set_antialias(Cairo::ANTIALIAS_SUBPIXEL);
364
365 // Update caches
367 auto const &[p0, p1, p2] = update_triangle_source();
368 auto const &[r_min, r_max] = get_radii();
369
370 // Paint with ring surface, clipping to ring.
371 cr->save();
372 cr->set_source(_source_ring, 0, 0);
373 cr->set_line_width (r_max - r_min);
374 cr->begin_new_path();
375 cr->arc(cx, cy, (r_max + r_min)/2.0, 0, 2.0 * M_PI);
376 cr->stroke();
377 cr->restore();
378 // Paint line on ring
379 auto color_on_ring = Color(Type::HSV, {_values[0], 1.0, 1.0});
380 double l = luminance(color_on_ring) < 0.5 ? 1.0 : 0.0;
381 cr->save();
382 cr->set_source_rgb(l, l, l);
383 cr->move_to(cx + cos(_values[0] * M_PI * 2.0) * (r_min + 1),
384 cy - sin(_values[0] * M_PI * 2.0) * (r_min + 1));
385 cr->line_to(cx + cos(_values[0] * M_PI * 2.0) * (r_max - 1),
386 cy - sin(_values[0] * M_PI * 2.0) * (r_max - 1));
387 cr->stroke();
388 cr->restore();
389
390 // Paint with triangle surface, clipping to triangle.
391 cr->save();
392 cr->set_source(_source_triangle, 0, 0);
393 cr->move_to(p0.x, p0.y);
394 cr->line_to(p1.x, p1.y);
395 cr->line_to(p2.x, p2.y);
396 cr->close_path();
397 cr->fill();
398 cr->restore();
399
400 // Draw marker
401 auto const &[mx, my] = get_marker_point();
402 double a = luminance(getColor()) < 0.5 ? 1.0 : 0.0;
403 cr->set_source_rgb(a, a, a);
404 cr->begin_new_path();
405 cr->arc(mx, my, marker_radius, 0, 2 * M_PI);
406 cr->stroke();
407
408 // Draw focus
410 // The focus_dash width & alpha(foreground_color) are from GTK3 Adwaita.
411 cr->set_dash(focus_dash, 0);
412 cr->set_line_width(1.0);
413
414 if (_focus_on_ring) {
415 auto const rgba = change_alpha(Widget::get_color(), 0.7);
416 Gdk::Cairo::set_source_rgba(cr, rgba);
417 cr->begin_new_path();
418 cr->rectangle(0, 0, width, height);
419 } else {
420 cr->set_source_rgb(1 - a, 1 - a, 1 - a);
421 cr->begin_new_path();
422 cr->arc(mx, my, marker_radius + focus_padding, 0, 2 * M_PI);
423 }
424
425 cr->stroke();
426 }
427}
428
429std::optional<bool> ColorWheelHSL::focus(Gtk::DirectionType const direction)
430{
431 // Any focus change must update focus indicators (add or remove).
433
434 // In forward direction, focus passes from no focus to ring focus to triangle
435 // focus to no focus.
436 if (!drawing_area_has_focus()) {
437 _focus_on_ring = (direction == Gtk::DirectionType::TAB_FORWARD);
439 return true;
440 }
441
442 // Already have focus
443 bool keep_focus = true;
444
445 switch (direction) {
446 case Gtk::DirectionType::TAB_BACKWARD:
447 if (!_focus_on_ring) {
448 _focus_on_ring = true;
449 } else {
450 keep_focus = false;
451 }
452 break;
453
454 case Gtk::DirectionType::TAB_FORWARD:
455 if (_focus_on_ring) {
456 _focus_on_ring = false;
457 } else {
458 keep_focus = false;
459 }
460 }
461
462 return keep_focus;
463}
464
465bool ColorWheelHSL::_set_from_xy(double const x, double const y)
466{
467 auto const [width, height] = *_cache_size;
468 double const cx = width/2.0;
469 double const cy = height/2.0;
470
471 double const r = std::min(cx, cy) * (1 - _ring_width);
472
473 // We calculate RGB value under the cursor by rotating the cursor
474 // and triangle by the hue value and looking at position in the
475 // now right pointing triangle.
476 double angle = _values[0] * 2 * M_PI;
477 double sin = std::sin(angle);
478 double cos = std::cos(angle);
479 double xp = ((x - cx) * cos - (y - cy) * sin) / r;
480 double yp = ((x - cx) * sin + (y - cy) * cos) / r;
481
482 double xt = lerp(0.0, 1.0, -0.5, 1.0, xp);
483 xt = std::clamp(xt, 0.0, 1.0);
484
485 double dy = (1-xt) * std::cos(M_PI / 6.0);
486 double yt = lerp(0.0, 1.0, -dy, dy, yp);
487 yt = std::clamp(yt, 0.0, 1.0);
488
489 ColorPoint c0(0, 0, Color(Type::RGB, {yt, yt, yt})); // Grey point along base.
490 ColorPoint c1(0, 0, Color(Type::HSV, {_values[0], 1, 1})); // Hue point at apex
491 ColorPoint c = lerp(c0, c1, 0, 1, xt);
492 c.color.setOpacity(_values.getOpacity()); // Remember opacity
493 return setColor(c.color, false); // Don't override previous hue.
494}
495
496bool ColorWheelHSL::set_from_xy_delta(double const dx, double const dy)
497{
498 auto [mx, my] = get_marker_point();
499 mx += dx;
500 my += dy;
501 return _set_from_xy(mx, my);
502}
503
504bool ColorWheelHSL::_is_in_ring(double x, double y)
505{
506 auto const [width, height] = *_cache_size;
507 auto const cx = width / 2.0;
508 auto const cy = height / 2.0;
509
510 auto const &[r_min, r_max] = get_radii();
511 double r2_max = r_max * r_max;
512 double r2_min = r_min * r_min;
513
514 double dx = x - cx;
515 double dy = y - cy;
516 double r2 = dx * dx + dy * dy;
517
518 return (r2_min < r2 && r2 < r2_max);
519}
520
521bool ColorWheelHSL::_is_in_triangle(double x, double y)
522{
523 auto const &[p0, p1, p2] = get_triangle_corners();
524 auto const &[x0, y0] = p0.get_xy();
525 auto const &[x1, y1] = p1.get_xy();
526 auto const &[x2, y2] = p2.get_xy();
527
528 double det = (x2 - x1) * (y0 - y1) - (y2 - y1) * (x0 - x1);
529 double s = ((x - x1) * (y0 - y1) - (y - y1) * (x0 - x1)) / det;
530 if (s < 0.0) return false;
531
532 double t = ((x2 - x1) * (y - y1) - (y2 - y1) * (x - x1)) / det;
533 return (t >= 0.0 && s + t <= 1.0);
534}
535
536void ColorWheelHSL::_update_ring_color(double x, double y)
537{
538 auto const [width, height] = *_cache_size;
539 double cx = width / 2.0;
540 double cy = height / 2.0;
541
542 double angle = -atan2(y - cy, x - cx);
543 if (angle < 0) {
544 angle += 2.0 * M_PI;
545 }
546 angle /= 2.0 * M_PI;
547
548 if (_values.set(0, angle)) {
549 _triangle_corners.reset();
551 }
552}
553
554Gtk::EventSequenceState ColorWheelHSL::on_click_pressed(Gtk::GestureClick const &controller,
555 int /*n_press*/, double x, double y)
556{
557 if (_is_in_ring(x, y) ) {
558 _adjusting = true;
561 _focus_on_ring = true;
562 _update_ring_color(x, y);
563 return Gtk::EventSequenceState::CLAIMED;
564 } else if (_is_in_triangle(x, y)) {
565 _adjusting = true;
568 _focus_on_ring = false;
569 _set_from_xy(x, y);
570 return Gtk::EventSequenceState::CLAIMED;
571 }
572
573 return Gtk::EventSequenceState::NONE;
574}
575
576Gtk::EventSequenceState ColorWheelHSL::on_click_released(int /*n_press*/, double /*x*/, double /*y*/)
577{
579 _adjusting = false;
580 return Gtk::EventSequenceState::CLAIMED;
581}
582
583void ColorWheelHSL::on_motion(Gtk::EventControllerMotion const &motion, double x, double y)
584{
585 if (!_adjusting) return;
586 auto state = motion.get_current_event_state();
587 if (!Controller::has_flag(state, Gdk::ModifierType::BUTTON1_MASK)) {
588 // lost button release event
590 _adjusting = false;
591 return;
592 }
593
594 if (_mode == DragMode::HUE) {
595 _update_ring_color(x, y);
596 } else if (_mode == DragMode::SATURATION_VALUE) {
597 _set_from_xy(x, y);
598 }
599}
600
601bool ColorWheelHSL::on_key_pressed(unsigned keyval, unsigned /*keycode*/, Gdk::ModifierType state)
602{
603 static constexpr double delta_hue = 2.0 / MAX_HUE;
604 auto dx = 0.0, dy = 0.0;
605
606 switch (keyval) {
607 case GDK_KEY_Up:
608 case GDK_KEY_KP_Up:
609 dy = -1.0;
610 break;
611
612 case GDK_KEY_Down:
613 case GDK_KEY_KP_Down:
614 dy = +1.0;
615 break;
616
617 case GDK_KEY_Left:
618 case GDK_KEY_KP_Left:
619 dx = -1.0;
620 break;
621
622 case GDK_KEY_Right:
623 case GDK_KEY_KP_Right:
624 dx = +1.0;
625 }
626
627 if (dx == 0.0 && dy == 0.0) return false;
628
629 bool changed = false;
630 if (_focus_on_ring) {
631 changed = _values.set(0, _values[0] - ((dx != 0 ? dx : dy) * delta_hue));
632 } else {
633 changed = set_from_xy_delta(dx, dy);
634 }
635
637
638 if (changed) {
640 }
641
642 return changed;
643}
644
646{
647 if (_radii) return *_radii;
648
649 // Force calc others, too.
650 _triangle_corners.reset();
651
652 _radii.emplace();
653 auto &[r_min, r_max] = *_radii;
654 auto const [width, height] = *_cache_size;
655 r_max = std::min(width, height) / 2.0 - 2 * (focus_line_width + focus_padding);
656 r_min = r_max * (1.0 - _ring_width);
657 return *_radii;
658}
659
660std::array<ColorPoint, 3> const &ColorWheelHSL::get_triangle_corners()
661{
663
664 auto const [width, height] = *_cache_size;
665 double const cx = width / 2.0;
666 double const cy = height / 2.0;
667
668 auto const &[r_min, r_max] = get_radii();
669 double angle = _values[0] * 2.0 * M_PI;
670 auto const add2 = 2.0 * M_PI / 3.0;
671 auto const angle2 = angle + add2;
672 auto const angle4 = angle2 + add2;
673
674 // Force calc this too
675 _marker_point.reset();
676
677 _triangle_corners.emplace();
678 auto &[p0, p1, p2] = *_triangle_corners;
679 auto const x0 = cx + std::cos(angle ) * r_min;
680 auto const y0 = cy - std::sin(angle ) * r_min;
681 auto const x1 = cx + std::cos(angle2) * r_min;
682 auto const y1 = cy - std::sin(angle2) * r_min;
683 auto const x2 = cx + std::cos(angle4) * r_min;
684 auto const y2 = cy - std::sin(angle4) * r_min;
685 p0 = {x0, y0, Color(Type::HSV, {_values[0], 1.0, 1.0})};
686 p1 = {x1, y1, Color(Type::HSV, {_values[0], 1.0, 0.0})};
687 p2 = {x2, y2, Color(Type::HSV, {_values[0], 0.0, 1.0})};
688 return *_triangle_corners;
689}
690
692{
693 if (_marker_point) return *_marker_point;
694
695 auto const &[p0, p1, p2] = get_triangle_corners();
696 auto const &[x0, y0] = p0.get_xy();
697 auto const &[x1, y1] = p1.get_xy();
698 auto const &[x2, y2] = p2.get_xy();
699
700 _marker_point.emplace();
701 auto &[mx, my] = *_marker_point;
702 auto const v1v2 = _values[1] * _values[2];
703 mx = x1 + (x2 - x1) * _values[2] + (x0 - x2) * v1v2;
704 my = y1 + (y2 - y1) * _values[2] + (y0 - y2) * v1v2;
705 return *_marker_point;
706}
707
709 : Glib::ObjectBase{"ColorWheelHSL"}
711 // All the calculations are based on HSV, not HSL
712 , ColorWheelBase(Type::HSV, {0, 0, 0, 1})
713{}
714
715ColorWheelHSL::ColorWheelHSL(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder) :
716 ColorWheelBase(cobject, builder, Type::HSV, {0, 0, 0, 1}) {}
717
718/* HSLuv Color Wheel */
719
721 : ColorWheelBase(Type::HSLUV, {0, 1, 0.5, 1})
722{
723 _picker_geometry = std::make_unique<PickerGeometry>();
724}
725
726ColorWheelHSLuv::ColorWheelHSLuv(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder) :
727 ColorWheelBase(cobject, builder, Type::HSLUV, {0, 1, 0.5, 1}) {
728
729 _picker_geometry = std::make_unique<PickerGeometry>();
730}
731
733 bool /*overrideHue*/, bool const emit)
734{
735 if (_values.set(color, true)) {
736 assert(_values.getSpace()->getType() == Type::HSLUV);
738 _scale = OUTER_CIRCLE_RADIUS / _picker_geometry->outer_circle_radius;
740 if (emit)
742 else
744 return true;
745 }
746 return false;
747}
748
753{
754 // Separate from the extremes to avoid overlapping intersections
755 double lightness = std::clamp((_values[2] * 100) + 0.01, 0.1, 99.9);
756
757 // Find the lines bounding the gamut polygon
758 auto const lines = Space::HSLuv::get_bounds(lightness);
759
760 // Find the line closest to origin
761 Geom::Line const *closest_line = nullptr;
762 double closest_distance = -1;
763
764 for (auto const &line : lines) {
765 double d = Geom::distance(Geom::Point(0, 0), line);
766 if (closest_distance < 0 || d < closest_distance) {
767 closest_distance = d;
768 closest_line = &line;
769 }
770 }
771
772 g_assert(closest_line);
773 auto const nearest_time = closest_line->nearestTime(Geom::Point(0, 0));
774 Geom::Angle start_angle{closest_line->pointAt(nearest_time)};
775
776 constexpr auto num_lines = 6;
777 constexpr auto max_intersections = num_lines * (num_lines - 1) / 2;
778 std::vector<Intersection> intersections;
779 intersections.reserve(max_intersections);
780
781 for (int i = 0; i < num_lines - 1; i++) {
782 for (int j = i + 1; j < num_lines; j++) {
783 auto xings = lines[i].intersect(lines[j]);
784 if (xings.empty()) {
785 continue;
786 }
787 intersections.emplace_back(i, j, xings.front().point(), start_angle);
788 }
789 }
790
791 std::sort(intersections.begin(), intersections.end(), [](Intersection const &lhs, Intersection const &rhs) {
792 return lhs.relative_angle.radians0() >= rhs.relative_angle.radians0();
793 });
794
795 // Find the relevant vertices of the polygon, in the counter-clockwise order.
796 std::vector<Geom::Point> ordered_vertices;
797 ordered_vertices.reserve(intersections.size());
798 double circumradius = 0.0;
799 unsigned current_index = closest_line - &lines[0];
800
801 for (auto const &intersection : intersections) {
802 if (intersection.line1 == current_index) {
803 current_index = intersection.line2;
804 } else if (intersection.line2 == current_index) {
805 current_index = intersection.line1;
806 } else {
807 continue;
808 }
809 ordered_vertices.emplace_back(intersection.point);
810 circumradius = std::max(circumradius, intersection.point.length());
811 }
812
813 _picker_geometry->vertices = std::move(ordered_vertices);
814 _picker_geometry->outer_circle_radius = circumradius;
815 _picker_geometry->inner_circle_radius = closest_distance;
816}
817
818static Geom::IntPoint _getMargin(Gtk::Allocation const &allocation)
819{
820 int const width = allocation.get_width();
821 int const height = allocation.get_height();
822
823 return {std::max(0, (width - height) / 2),
824 std::max(0, (height - width) / 2)};
825}
826
827inline static Geom::IntPoint _getAllocationDimensions(Gtk::Allocation const &allocation)
828{
829 return {allocation.get_width(), allocation.get_height()};
830}
831
832inline static int _getAllocationSize(Gtk::Allocation const &allocation)
833{
834 return std::min(allocation.get_width(), allocation.get_height());
835}
836
839{
840 return _values[2] < VERTEX_EPSILON || _values[2] > 1.0 - VERTEX_EPSILON;
841}
842
843void ColorWheelHSLuv::on_drawing_area_draw(::Cairo::RefPtr<::Cairo::Context> const &cr, int, int)
844{
845 auto const &allocation = get_drawing_area_allocation();
846 auto dimensions = _getAllocationDimensions(allocation);
847 auto center = (0.5 * (Geom::Point)dimensions).floor();
848
849 auto size = _getAllocationSize(allocation);
850 double const resize = size / static_cast<double>(SIZE);
851
852 auto const margin = _getMargin(allocation);
853 auto polygon_vertices_px = to_pixel_coordinate(_picker_geometry->vertices, _scale, resize);
854 for (auto &point : polygon_vertices_px) {
855 point += margin;
856 }
857
858 bool const is_vertex = _vertex();
859 cr->set_antialias(Cairo::ANTIALIAS_SUBPIXEL);
860
861 if (size > _square_size && !polygon_vertices_px.empty()) {
862 if (_cache_size != dimensions) {
864 }
865 if (!is_vertex) {
866 // Paint with surface, clipping to polygon
867 cr->save();
868 cr->set_source(_surface_polygon, 0, 0);
869 auto it = polygon_vertices_px.begin();
870 cr->move_to((*it)[Geom::X], (*it)[Geom::Y]);
871 for (++it; it != polygon_vertices_px.end(); ++it) {
872 cr->line_to((*it)[Geom::X], (*it)[Geom::Y]);
873 }
874 cr->close_path();
875 cr->fill();
876 cr->restore();
877 }
878 }
879
880 // Draw foreground
881
882 // Outer circle
883 std::vector<double> dashes{OUTER_CIRCLE_DASH_SIZE};
884 cr->set_line_width(1);
885 // White dashes
886 cr->set_source_rgb(1.0, 1.0, 1.0);
887 cr->set_dash(dashes, 0.0);
888 cr->begin_new_path();
889 cr->arc(center[Geom::X], center[Geom::Y], _scale * resize * _picker_geometry->outer_circle_radius, 0, 2 * M_PI);
890 cr->stroke();
891 // Black dashes
892 cr->set_source_rgb(0.0, 0.0, 0.0);
893 cr->set_dash(dashes, OUTER_CIRCLE_DASH_SIZE);
894 cr->begin_new_path();
895 cr->arc(center[Geom::X], center[Geom::Y], _scale * resize * _picker_geometry->outer_circle_radius, 0, 2 * M_PI);
896 cr->stroke();
897 cr->unset_dash();
898
899 // Contrast
900 auto [gray, alpha] = get_contrasting_color(perceptual_lightness(_values[2]));
901 cr->set_source_rgba(gray, gray, gray, alpha);
902
903 // Draw inscribed circle
904 double const inner_stroke_width = 2.0;
905 double inner_radius = is_vertex ? 0.01 : _picker_geometry->inner_circle_radius;
906 cr->set_line_width(inner_stroke_width);
907 cr->begin_new_path();
908 cr->arc(center[Geom::X], center[Geom::Y], _scale * resize * inner_radius, 0, 2 * M_PI);
909 cr->stroke();
910
911 // Center
912 cr->begin_new_path();
913 cr->arc(center[Geom::X], center[Geom::Y], 2, 0, 2 * M_PI);
914 cr->fill();
915
916 // Draw marker
917 auto luv = Luv::toCoordinates(_values.converted(Type::LUV)->getValues());
918 auto mp = to_pixel_coordinate({luv[1], luv[2]}, _scale, resize) + margin;
919
920 cr->set_line_width(inner_stroke_width);
921 cr->begin_new_path();
922 cr->arc(mp[Geom::X], mp[Geom::Y], marker_radius, 0, 2 * M_PI);
923 cr->stroke();
924
925 // Focus
927 cr->set_dash(focus_dash, 0);
928 cr->set_line_width(focus_line_width);
929 cr->set_source_rgb(1 - gray, 1 - gray, 1 - gray);
930 cr->begin_new_path();
931 cr->arc(mp[Geom::X], mp[Geom::Y], marker_radius + focus_padding, 0, 2 * M_PI);
932 cr->stroke();
933 }
934}
935
936bool ColorWheelHSLuv::_set_from_xy(double const x, double const y)
937{
938 auto const allocation = get_drawing_area_allocation();
939 int const width = allocation.get_width();
940 int const height = allocation.get_height();
941
942 double const resize = std::min(width, height) / static_cast<double>(SIZE);
943 auto const p = from_pixel_coordinate(Geom::Point(x, y) - _getMargin(allocation), _scale, resize);
944
945 if (_values.set(Color(Type::LUV, Luv::fromCoordinates({_values[2] * 100, p[Geom::X], p[Geom::Y]})), true)) {
947 return true;
948 }
949 return false;
950}
951
953{
954 auto const allocation = get_drawing_area_allocation();
955 auto allocation_size = _getAllocationDimensions(allocation);
956 int const size = std::min(allocation_size[Geom::X], allocation_size[Geom::Y]);
957
958 // Update square size
959 _square_size = std::max(1, static_cast<int>(size / 50));
960 if (size < _square_size) {
961 return;
962 }
963
964 _cache_size = allocation_size;
965
966 double const resize = size / static_cast<double>(SIZE);
967
968 auto const margin = _getMargin(allocation);
969 auto polygon_vertices_px = to_pixel_coordinate(_picker_geometry->vertices, _scale, resize);
970
971 // Find the bounding rectangle containing all points (adjusted by the margin).
972 Geom::Rect bounding_rect;
973 for (auto const &point : polygon_vertices_px) {
974 bounding_rect.expandTo(point + margin);
975 }
976 bounding_rect *= Geom::Scale(1.0 / _square_size);
977
978 // Round to integer pixel coords
979 auto const bounding_max = bounding_rect.max().ceil();
980 auto const bounding_min = bounding_rect.min().floor();
981
982 int const stride = Cairo::ImageSurface::format_stride_for_width(Cairo::Surface::Format::RGB24, _cache_size.x());
983
984 _surface_polygon.reset();
985 _buffer_polygon.resize(_cache_size.y() * stride / 4);
986 std::vector<guint32> buffer_line(stride / 4);
987
988 auto const square_center = Geom::IntPoint(_square_size / 2, _square_size / 2);
989 std::vector<double> color_vals = {_values[2] * 100, 0, 0};
990
991 // Set the color of each pixel/square
992 for (int y = bounding_min[Geom::Y]; y < bounding_max[Geom::Y]; y++) {
993 for (int x = bounding_min[Geom::X]; x < bounding_max[Geom::X]; x++) {
994 auto pos = Geom::IntPoint(x * _square_size, y * _square_size);
995 auto point = from_pixel_coordinate(pos + square_center - margin, _scale, resize);
996 color_vals[1] = point[Geom::X];
997 color_vals[2] = point[Geom::Y];
998
999 auto color = Color(Type::LUV, Luv::fromCoordinates(color_vals));
1000 guint32 *p = buffer_line.data() + (x * _square_size);
1001 for (int i = 0; i < _square_size; i++) {
1002 p[i] = color.toARGB();
1003 }
1004 }
1005
1006 // Copy the line buffer to the surface buffer
1007 int const scaled_y = y * _square_size;
1008 for (int i = 0; i < _square_size; i++) {
1009 guint32 *t = _buffer_polygon.data() + (scaled_y + i) * (stride / 4);
1010 std::memcpy(t, buffer_line.data(), stride);
1011 }
1012 }
1013
1014 _surface_polygon = ::Cairo::ImageSurface::create(reinterpret_cast<unsigned char *>(_buffer_polygon.data()),
1015 Cairo::Surface::Format::RGB24, _cache_size.x(), _cache_size.y(), stride);
1016}
1017
1018Gtk::EventSequenceState ColorWheelHSLuv::on_click_pressed(Gtk::GestureClick const &,
1019 int /*n_press*/, double x, double y)
1020{
1021 auto const event_pt = Geom::Point(x, y);
1022 auto const allocation = get_drawing_area_allocation();
1023 int const size = _getAllocationSize(allocation);
1024 auto const region = Geom::IntRect::from_xywh(_getMargin(allocation), {size, size});
1025
1026 if (region.contains(event_pt.round())) {
1027 _adjusting = true;
1029 _setFromPoint(event_pt);
1030 return Gtk::EventSequenceState::CLAIMED;
1031 }
1032
1033 return Gtk::EventSequenceState::NONE;
1034}
1035
1036Gtk::EventSequenceState ColorWheelHSLuv::on_click_released(int /*n_press*/, double /*x*/, double /*y*/)
1037{
1038 _adjusting = false;
1039 return Gtk::EventSequenceState::CLAIMED;
1040}
1041
1042void ColorWheelHSLuv::on_motion(Gtk::EventControllerMotion const &/*motion*/,
1043 double x, double y)
1044{
1045 if (_adjusting) {
1046 _set_from_xy(x, y);
1047 }
1048}
1049
1050bool ColorWheelHSLuv::on_key_pressed(unsigned keyval, unsigned /*keycode*/, Gdk::ModifierType state)
1051{
1052 bool consumed = false;
1053
1054 // Get current point
1055 auto luv = *_values.converted(Type::LUV);
1056
1057 double const marker_move = 1.0 / _scale;
1058
1059 switch (keyval) {
1060 case GDK_KEY_Up:
1061 case GDK_KEY_KP_Up:
1062 luv.set(2, luv[2] + marker_move);
1063 consumed = true;
1064 break;
1065 case GDK_KEY_Down:
1066 case GDK_KEY_KP_Down:
1067 luv.set(2, luv[2] - marker_move);
1068 consumed = true;
1069 break;
1070 case GDK_KEY_Left:
1071 case GDK_KEY_KP_Left:
1072 luv.set(1, luv[1] - marker_move);
1073 consumed = true;
1074 break;
1075 case GDK_KEY_Right:
1076 case GDK_KEY_KP_Right:
1077 luv.set(1, luv[1] + marker_move);
1078 consumed = true;
1079 }
1080
1081 if (!consumed) return false;
1082
1083 _adjusting = true;
1084
1085 if (_values.set(luv, true))
1086 color_changed();
1087
1088 return true;
1089}
1090
1091/* ColorPoint */
1093 : x(0)
1094 , y(0)
1095 , color(0x0)
1096{}
1097
1098ColorPoint::ColorPoint(double x, double y, Color c)
1099 : x(x)
1100 , y(y)
1101 , color(std::move(c))
1102{}
1103
1104ColorPoint::ColorPoint(double x, double y, guint c)
1105 : x(x)
1106 , y(y)
1107 , color(c)
1108{}
1109
1110static double lerp(double v0, double v1, double t0, double t1, double t)
1111{
1112 double const s = (t0 != t1) ? (t - t0) / (t1 - t0) : 0.0;
1113 return Geom::lerp(s, v0, v1);
1114}
1115
1116static ColorPoint lerp(ColorPoint const &v0, ColorPoint const &v1, double t0, double t1,
1117 double t)
1118{
1119 double x = lerp(v0.x, v1.x, t0, t1, t);
1120 double y = lerp(v0.y, v1.y, t0, t1, t);
1121
1122 auto r0 = *v0.color.converted(Type::RGB);
1123 auto r1 = *v1.color.converted(Type::RGB);
1124 double r = lerp(r0[0], r1[0], t0, t1, t);
1125 double g = lerp(r0[1], r1[1], t0, t1, t);
1126 double b = lerp(r0[2], r1[2], t0, t1, t);
1127
1128 return ColorPoint(x, y, Color(Type::RGB, {r, g, b}));
1129}
1130
1131// N.B. We also have Color:get_perceptual_lightness(), but that uses different weightings..!
1132double luminance(Color const &color)
1133{
1134 auto c = *color.converted(Type::RGB);
1135 return (c[0] * 0.2125 + c[1] * 0.7154 + c[2] * 0.0721);
1136}
1137
1145static Geom::Point to_pixel_coordinate(Geom::Point const &point, double scale, double resize)
1146{
1147 return Geom::Point(
1148 point[Geom::X] * scale * resize + (SIZE * resize / 2.0),
1149 (SIZE * resize / 2.0) - point[Geom::Y] * scale * resize
1150 );
1151}
1152
1160static Geom::Point from_pixel_coordinate(Geom::Point const &point, double scale, double resize)
1161{
1162 return Geom::Point(
1163 (point[Geom::X] - (SIZE * resize / 2.0)) / (scale * resize),
1164 ((SIZE * resize / 2.0) - point[Geom::Y]) / (scale * resize)
1165 );
1166}
1167
1174static std::vector<Geom::Point> to_pixel_coordinate(std::vector<Geom::Point> const &points,
1175 double scale, double resize)
1176{
1177 std::vector<Geom::Point> result;
1178
1179 for (auto const &p : points) {
1180 result.emplace_back(to_pixel_coordinate(p, scale, resize));
1181 }
1182
1183 return result;
1184}
1185
1199void draw_vertical_padding(ColorPoint p0, ColorPoint p1, int padding, bool pad_upwards,
1200 guint32 *buffer, int height, int stride)
1201{
1202 // skip if horizontal padding is more accurate, e.g. if the edge is vertical
1203 double gradient = (p1.y - p0.y) / (p1.x - p0.x);
1204 if (std::abs(gradient) > 1.0) {
1205 return;
1206 }
1207
1208 double min_y = std::min(p0.y, p1.y);
1209 double max_y = std::max(p0.y, p1.y);
1210
1211 double min_x = std::min(p0.x, p1.x);
1212 double max_x = std::max(p0.x, p1.x);
1213
1214 // go through every point on the line
1215 for (int y = min_y; y <= max_y; ++y) {
1216 double start_x = lerp(p0, p1, p0.y, p1.y, std::clamp(static_cast<double>(y), min_y,
1217 max_y)).x;
1218 double end_x = lerp(p0, p1, p0.y, p1.y, std::clamp(static_cast<double>(y) + 1, min_y,
1219 max_y)).x;
1220 if (start_x > end_x) {
1221 std::swap(start_x, end_x);
1222 }
1223
1224 guint32 *p = buffer + y * stride;
1225 p += static_cast<int>(start_x);
1226 for (int x = start_x; x <= end_x; ++x) {
1227 // get the color at this point on the line
1228 ColorPoint point = lerp(p0, p1, p0.x, p1.x, std::clamp(static_cast<double>(x),
1229 min_x, max_x));
1230 // paint the padding vertically above or below this point
1231 for (int offset = 0; offset <= padding; ++offset) {
1232 if (pad_upwards && (point.y - offset) >= 0) {
1233 *(p - (offset * stride)) = point.color.toARGB();
1234 } else if (!pad_upwards && (point.y + offset) < height) {
1235 *(p + (offset * stride)) = point.color.toARGB();
1236 }
1237 }
1238 ++p;
1239 }
1240 }
1241}
1242
1243} // namespace Inkscape::UI::Widget
1244
1245/*
1246 Local Variables:
1247 mode:c++
1248 c-file-style:"stroustrup"
1249 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1250 indent-tabs-mode:nil
1251 fill-column:99
1252 End:
1253*/
1254// vim:filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8: textwidth=99:
double scale
Definition aa.cpp:228
Various trigoniometric helper functions.
Bin: widget that can hold one child, useful as a base class of custom widgets.
int margin
Definition canvas.cpp:166
Wrapper for angular values.
Definition angle.h:73
static CRect from_xywh(C x, C y, C w, C h)
Create rectangle from origin and dimensions.
void expandTo(CPoint const &p)
Enlarge the rectangle to contain the given point.
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
CPoint max() const
Get the corner of the rectangle with largest coordinate values.
Two-dimensional point with integer coordinates.
Definition int-point.h:57
constexpr IntCoord x() const noexcept
Definition int-point.h:77
constexpr IntCoord y() const noexcept
Definition int-point.h:79
Infinite line on a plane.
Definition line.h:53
Coord nearestTime(Point const &p) const
Find a point on the line closest to the query point.
Definition line.h:252
Point pointAt(Coord t) const
Definition line.h:231
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
Scaling from the origin.
Definition transforms.h:150
uint32_t toARGB(double opacity=1.0) const
Return the RGBA int32 as an ARGB format number.
Definition color.cpp:125
bool set(unsigned int index, double value)
Set a specific channel in the color.
Definition color.cpp:350
std::optional< Color > converted(Color const &other) const
Return a copy of this color converted to the same format as the other color.
Definition color.cpp:189
void normalize()
Make sure the values for this color are within acceptable ranges.
Definition color.cpp:470
double getOpacity() const
Get the opacity in this color, if it's stored.
Definition color.cpp:407
std::shared_ptr< Space::AnySpace > const & getSpace() const
Definition color.h:39
static std::array< Geom::Line, 6 > get_bounds(double l)
Calculate the bounds of the Luv colors in RGB gamut.
Definition hsluv.cpp:27
static std::vector< double > fromCoordinates(std::vector< double > const &in)
Definition luv.cpp:52
static std::vector< double > toCoordinates(std::vector< double > const &in)
Definition luv.cpp:59
The Bin is a widget that can hold a single child.
Definition bin.h:31
sigc::connection connectAfterResize(F &&slot)
Register a handler to run immediately after a resize operation.
Definition bin.h:59
void set_child(Gtk::Widget *child)
Sets (parents) the child widget, or unsets (unparents) it if child is null.
Definition bin.cpp:40
virtual Gtk::EventSequenceState on_click_released(int n_press, double x, double y)=0
void color_changed()
Call when color has changed! Emits signal_color_changed & calls _drawing_area->queue_draw()
sigc::signal< void()> _signal_color_changed
virtual Gtk::EventSequenceState on_click_pressed(Gtk::GestureClick const &controller, int n_press, double x, double y)=0
All event controllers are connected to the DrawingArea.
sigc::connection connect_color_changed(sigc::slot< void()>)
Connect a slot to be called after the color has changed.
virtual void on_drawing_area_draw(Cairo::RefPtr< Cairo::Context > const &cr, int, int)=0
virtual void on_drawing_area_size(int width, int height, int baseline)
virtual Colors::Color getColor() const
virtual bool on_key_pressed(unsigned keyval, unsigned keycode, Gdk::ModifierType state)
virtual void on_motion(Gtk::EventControllerMotion const &motion, double x, double y)=0
void _on_motion(Gtk::EventControllerMotion const &motion, double x, double y)
Gtk::Allocation get_drawing_area_allocation() const
void on_key_released(unsigned keyval, unsigned keycode, Gdk::ModifierType state)
ColorWheelBase(Colors::Space::Type type, std::vector< double > initial_color)
void _update_ring_color(double x, double y)
std::array< ColorPoint, 3 > TriangleCorners
std::optional< TriangleCorners > _triangle_corners
bool setColor(Colors::Color const &color, bool overrideHue=true, bool emit=true) override
Set the RGB of the wheel.
bool set_from_xy_delta(double dx, double dy)
void on_drawing_area_size(int width, int height, int baseline) override
bool _set_from_xy(double x, double y)
Gtk::EventSequenceState on_click_released(int n_press, double x, double y) final
std::vector< guint32 > _buffer_triangle
std::optional< Geom::Point > _marker_point
static constexpr double _ring_width
Cairo::RefPtr< Cairo::ImageSurface > _source_ring
bool on_key_pressed(unsigned keyval, unsigned keycode, Gdk::ModifierType state) final
std::optional< Geom::IntPoint > _cache_size
TriangleCorners const & get_triangle_corners()
void on_motion(Gtk::EventControllerMotion const &motion, double x, double y) final
void on_drawing_area_draw(Cairo::RefPtr< Cairo::Context > const &cr, int, int) override
bool _is_in_triangle(double x, double y)
std::optional< bool > focus(Gtk::DirectionType direction) override
Called before gtk_widget_focus(): return true if moving in direction keeps focus w/in self,...
Gtk::EventSequenceState on_click_pressed(Gtk::GestureClick const &controller, int n_press, double x, double y) final
All event controllers are connected to the DrawingArea.
Cairo::RefPtr< Cairo::ImageSurface > _source_triangle
void on_motion(Gtk::EventControllerMotion const &motion, double x, double y) final
bool on_key_pressed(unsigned keyval, unsigned keycode, Gdk::ModifierType state) final
Gtk::EventSequenceState on_click_pressed(Gtk::GestureClick const &controller, int n_press, double x, double y) final
All event controllers are connected to the DrawingArea.
void on_drawing_area_draw(Cairo::RefPtr< Cairo::Context > const &cr, int, int) final
bool _set_from_xy(double const x, double const y)
Gtk::EventSequenceState on_click_released(int n_press, double x, double y) final
bool setColor(Colors::Color const &color, bool overrideHue=true, bool emit=true) override
See base doc & N.B. that overrideHue is unused by this class.
void updateGeometry()
Update the PickerGeometry structure owned by the instance.
Cairo::RefPtr<::Cairo::ImageSurface > _surface_polygon
std::unique_ptr< PickerGeometry > _picker_geometry
void _setFromPoint(Geom::Point const &pt)
bool _vertex() const
Detect whether we're at the top or bottom vertex of the color space.
A class you can inherit to access GTK4ʼs Widget.css_changed & .focus vfuncs, missing in gtkmm4.
Color item used in palettes and swatches UI.
static T det(T a, T b, T c, T d)
Definition conic-5.cpp:95
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
Integral and real coordinate types and some basic utilities.
Css & result
double c[8][4]
unsigned int guint32
constexpr Coord lerp(Coord t, Coord a, Coord b)
Numerically stable linear interpolation.
Definition coord.h:97
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
double offset
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
Definition desktop.h:50
A set of useful color modifying functions which do not fit as generic methods on the color class itse...
Definition profile.cpp:24
std::pair< double, double > get_contrasting_color(double l)
Definition utils.cpp:198
double perceptual_lightness(double l)
Definition utils.cpp:185
double lightness(Color color)
auto use_state(Slot &&slot)
Definition controller.h:43
bool has_flag(Gdk::ModifierType const state, Gdk::ModifierType const flags)
Helper to query if ModifierType state contains one or more of given flag(s).
Definition controller.h:25
Custom widgets.
Definition desktop.h:126
static Geom::IntPoint _getMargin(Gtk::Allocation const &allocation)
static constexpr double OUTER_CIRCLE_DASH_SIZE
static int _getAllocationSize(Gtk::Allocation const &allocation)
static constexpr double MIN_HUE
static Geom::Point to_pixel_coordinate(Geom::Point const &point, double scale, double resize)
Convert a point of the gamut color polygon (Luv) to pixel coordinates.
static constexpr double MAX_HUE
static constexpr double VERTEX_EPSILON
static constexpr int height
static constexpr double focus_line_width
static double luminance(Color const &color)
static void draw_vertical_padding(ColorPoint p0, ColorPoint p1, int padding, bool pad_upwards, guint32 *buffer, int height, int stride)
Paints padding for an edge of the triangle, using the (vertically) closest point.
static constexpr int const OUTER_CIRCLE_RADIUS
static Geom::IntPoint _getAllocationDimensions(Gtk::Allocation const &allocation)
static constexpr double focus_padding
static double lerp(double v0, double v1, double t0, double t1, double t)
static constexpr double MIN_SATURATION
static constexpr double marker_radius
static constexpr int const SIZE
static auto const focus_dash
static Geom::Point from_pixel_coordinate(Geom::Point const &point, double scale, double resize)
Convert a point in pixels on the widget to Luv coordinates.
static constexpr double MIN_LIGHTNESS
static constexpr double MAX_LIGHTNESS
static constexpr double MAX_SATURATION
static Geom::Point direction(Geom::Point const &first, Geom::Point const &second)
Computes an unit vector of the direction from first to second control point.
Definition node.cpp:164
STL namespace.
static cairo_user_data_key_t key
int stride
void resize()
Definition resize.cpp:80
static const Point data[]
double width
Glib::RefPtr< Gtk::Builder > builder
Gdk::RGBA change_alpha(const Gdk::RGBA &color, double new_alpha)
Definition util.cpp:402
Geom::IntPoint dimensions(const Cairo::RefPtr< Cairo::ImageSurface > &surface)
Definition util.cpp:353