Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
oklab-color-wheel.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
5/*
6 * Authors:
7 * Rafael Siejakowski <rs@rs-math.net>
8 *
9 * Copyright (C) 2022 Authors
10 *
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
15
16#include <algorithm>
17#include <cmath>
18#include <gtkmm/gestureclick.h>
19
20#include "colors/spaces/enum.h"
21#include "colors/spaces/oklch.h"
22#include "colors/utils.h"
23#include "display/cairo-utils.h"
24
25using namespace Inkscape::Colors;
26
27namespace Inkscape::UI::Widget {
28
30 : ColorWheelBase(Space::Type::OKHSL, {0, 0, 0, 1})
31{}
32
33bool OKWheel::setColor(Color const &color,
34 bool /*overrideHue*/, bool const emit)
35{
36 if (_values.set(color, true)) {
40 if (emit)
42 return true;
43 }
44 return false;
45}
46
48{
49 return _values;
50}
51
59{
60 double const angle_step = 360.0 / CHROMA_BOUND_SAMPLES;
61 double hue_angle_deg = 0.0;
62 for (unsigned i = 0; i < CHROMA_BOUND_SAMPLES; i++) {
63 _bounds[i] = Space::OkLch::max_chroma(_values[L], hue_angle_deg);
64 hue_angle_deg += angle_step;
65 }
66}
67
74{
75 auto const &allocation = get_drawing_area_allocation();
76 auto width = allocation.get_width();
77 auto height = allocation.get_height();
78 double new_radius = 0.5 * std::min(width, height);
79 // Allow the halo to fit at coordinate extrema.
80 new_radius -= HALO_RADIUS + 0.5 * HALO_STROKE;
81 bool disc_needs_redraw = (_disc_radius != new_radius);
82 _disc_radius = new_radius;
83 _margin = {std::max(0.0, 0.5 * (width - 2.0 * _disc_radius)),
84 std::max(0.0, 0.5 * (height - 2.0 * _disc_radius))};
85 return disc_needs_redraw;
86}
87
98uint32_t OKWheel::_discColor(Geom::Point const &point) const
99{
100 double saturation = point.length();
101 if (saturation == 0.0) {
102 return Color(Space::Type::OKLCH, {_values[L], 0, 0}).toARGB();
103 } else if (saturation > 1.0) {
104 saturation = 1.0;
105 }
106
107 double const hue_radians = Geom::Angle(Geom::atan2(point)).radians0();
108
109 // Find the precomputed chroma bounds on both sides of this angle.
110 unsigned previous_sample = std::floor(hue_radians * 0.5 * CHROMA_BOUND_SAMPLES / M_PI);
111 if (previous_sample >= CHROMA_BOUND_SAMPLES) {
112 previous_sample = 0;
113 }
114 unsigned const next_sample = (previous_sample == CHROMA_BOUND_SAMPLES - 1) ? 0 : previous_sample + 1;
115 double const previous_sample_angle = 2.0 * M_PI * previous_sample / CHROMA_BOUND_SAMPLES;
116 double const angle_delta = hue_radians - previous_sample_angle;
117 double const t = angle_delta * 0.5 * CHROMA_BOUND_SAMPLES / M_PI;
118 double const chroma_bound_estimate = Geom::lerp(t, _bounds[previous_sample], _bounds[next_sample]);
119 double const absolute_chroma = chroma_bound_estimate * saturation;
120
121 return Color(Space::Type::OKLCH, {_values[L], absolute_chroma, Geom::deg_from_rad(hue_radians) / 360}).toARGB();
122}
123
137
139void OKWheel::on_drawing_area_draw(Cairo::RefPtr<Cairo::Context> const &cr, int, int)
140{
141 if(_updateDimensions()) {
142 _redrawDisc();
143 }
144
145 cr->save();
146 cr->set_antialias(Cairo::ANTIALIAS_SUBPIXEL);
147
148 // Draw the colorful disc background from the cached pixbuf,
149 // clipping to a geometric circle (avoids aliasing).
150 cr->translate(_margin[Geom::X], _margin[Geom::Y]);
151 cr->move_to(2 * _disc_radius, _disc_radius);
152 cr->arc(_disc_radius, _disc_radius, _disc_radius, 0.0, 2.0 * M_PI);
153 cr->close_path();
154 cr->set_source(_disc, 0, 0);
155 cr->fill();
156
157 // Draw the halo around the current color.
158 {
159 auto const where = _curColorWheelCoords();
160 cr->translate(_disc_radius, _disc_radius);
161 cr->move_to(where.x() + HALO_RADIUS, where.y());
162 cr->arc(where.x(), where.y(), HALO_RADIUS, 0.0, 2.0 * M_PI);
163 cr->close_path();
164 // Fill the halo with the current color.
165 {
167 }
168 cr->fill_preserve();
169
170 // Stroke the border of the halo.
171 {
172 auto [gray, alpha] = get_contrasting_color(_values[L]);
173 cr->set_source_rgba(gray, gray, gray, alpha);
174 }
175 cr->set_line_width(HALO_STROKE);
176 cr->stroke();
177 }
178 cr->restore();
179}
180
183{
184 int const size = std::ceil(2.0 * _disc_radius);
185 double const radius = 0.5 * size;
186 double const inverse_radius = 1.0 / radius;
187
188 if (!_disc || _disc->get_height() != size) {
189 _disc = Cairo::ImageSurface::create(Cairo::Surface::Format::RGB24, size, size);
190 }
191
192 // Fill buffer with (<don't care>, R, G, B) values.
193 uint32_t *pos = reinterpret_cast<uint32_t *>(_disc->get_data());
194 g_assert(pos);
195
196 for (int y = 0; y < size; y++) {
197 // Convert (x, y) to a coordinate system where the
198 // disc is the unit disc and the y-axis points up.
199 double const normalized_y = inverse_radius * (radius - y);
200 for (int x = 0; x < size; x++) {
201 *pos++ = _discColor({inverse_radius * (x - radius), normalized_y});
202 }
203 }
204}
205
210{
211 auto result = event_pt - _margin - Geom::Point(_disc_radius, _disc_radius);
212 double const scale = 1.0 / _disc_radius;
213 return result * Geom::Scale(scale, -scale);
214}
215
221bool OKWheel::_setColor(Geom::Point const &pt, bool const emit)
222{
223 Geom::Angle clicked_hue = _values[S] ? Geom::atan2(pt) : 0.0;
224
225 bool s = _values.set(S, pt.length());
226 bool h = _values.set(H, Geom::deg_from_rad(clicked_hue.radians0()) / 360);
227 if (s || h) {
229 if (emit)
231 return true;
232 }
233 return false;
234}
235
243{
244 auto r = pt.length();
245 if (r > 1.0) { // Clicked outside the disc, no cookie.
246 return false;
247 }
248 _adjusting = true;
249 _setColor(pt);
250 return true;
251}
252
254Gtk::EventSequenceState OKWheel::on_click_pressed(Gtk::GestureClick const &click,
255 int /*n_press*/, double const x, double const y)
256{
257 if (click.get_current_button() == 1) {
258 // Convert the click coordinates to the abstract coords in which
259 // the picker disc is the unit disc in the xy-plane.
260 if (_onClick(_event2abstract({x, y}))) {
261 return Gtk::EventSequenceState::CLAIMED;
262 }
263 }
264 // TODO: add a context menu to copy out the CSS4 color values.
265 return Gtk::EventSequenceState::NONE;
266}
267
269Gtk::EventSequenceState OKWheel::on_click_released(int /*n_press*/, double /*x*/, double /*y*/)
270{
271 _adjusting = false;
272 return Gtk::EventSequenceState::CLAIMED;
273}
274
276void OKWheel::on_motion(Gtk::EventControllerMotion const &/*motion*/, double x, double y)
277{
278 if (_adjusting) {
279 _setColor(_event2abstract({x, y}));
280 }
281}
282
283} // namespace Inkscape::UI::Widget
284
285/*
286 Local Variables:
287 mode:c++
288 c-file-style:"stroustrup"
289 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
290 indent-tabs-mode:nil
291 fill-column:99
292 End:
293*/
294// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
void ink_cairo_set_source_color(Cairo::RefPtr< Cairo::Context > &ctx, Colors::Color const &color, bool to_srgb)
Set the source color of the Cairo context.
Cairo integration helpers.
Wrapper for angular values.
Definition angle.h:73
static Angle from_degrees(Coord d)
Create an angle from its measure in degrees.
Definition angle.h:136
Coord radians0() const
Get the angle as positive radians.
Definition angle.h:112
Two-dimensional point that doubles as a vector.
Definition point.h:66
Coord length() const
Compute the distance from origin.
Definition point.h:118
Scaling from the origin.
Definition transforms.h:150
bool set(unsigned int index, double value)
Set a specific channel in the color.
Definition color.cpp:350
void normalize()
Make sure the values for this color are within acceptable ranges.
Definition color.cpp:470
static double max_chroma(double l, double h)
Definition oklch.cpp:207
void color_changed()
Call when color has changed! Emits signal_color_changed & calls _drawing_area->queue_draw()
Gtk::Allocation get_drawing_area_allocation() const
Colors::Color getColor() const override
Geom::Point _event2abstract(Geom::Point const &point) const
Convert widget (event) coordinates to an abstract coordinate system in which the picker disc is the u...
static double constexpr HALO_RADIUS
Radius of the halo around the current color.
void on_motion(Gtk::EventControllerMotion const &motion, double x, double y) final
Handle a drag (motion notify event).
bool _setColor(Geom::Point const &pt, bool emit=true)
Set the current color based on a point on the wheel.
void _updateChromaBounds()
Compute the chroma bounds around the picker disc.
uint32_t _discColor(Geom::Point const &point) const
Compute the ARGB32 color for a point inside the picker disc.
Gtk::EventSequenceState on_click_pressed(Gtk::GestureClick const &controller, int n_press, double x, double y) final
Handle a button press event.
bool _updateDimensions()
Update the size of the color disc and margins depending on the widget's allocation.
static double constexpr HALO_STROKE
Width of the halo's stroke.
std::array< double, CHROMA_BOUND_SAMPLES > _bounds
Geom::Point _curColorWheelCoords() const
Returns the position of the current color in the coordinates of the picker wheel.
static unsigned constexpr CHROMA_BOUND_SAMPLES
How many samples for the chroma bounds to use for the color disc.
static unsigned constexpr L
Indices into _values.
Gtk::EventSequenceState on_click_released(int n_press, double x, double y) final
Handle a button release event.
void _redrawDisc()
Recreate the pixel buffer containing the colourful disc.
static unsigned constexpr H
void on_drawing_area_draw(Cairo::RefPtr< Cairo::Context > const &cr, int, int) override
Draw the widget into the Cairo context.
bool _onClick(Geom::Point const &unit_pos)
Handle a left mouse click on the widget.
bool setColor(Colors::Color const &color, bool overrideHue=true, bool emit=true) override
Set the displayed color to the specified gamma-compressed sRGB color.
Cairo::RefPtr< Cairo::ImageSurface > _disc
static unsigned constexpr S
Css & result
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
void sincos(double angle, double &sin_, double &cos_)
Simultaneously compute a sine and a cosine of the same angle.
Definition math-utils.h:89
double atan2(Point const &p)
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
Custom widgets.
Definition desktop.h:126
static constexpr int height
double width