/*
9 * Massinissa Derriche <massinissa.derriche@gmail.com>
11 * Copyright (C) 2018, 2021 Authors
13 * Released under GNU GPL v2+, read the file
'COPYING' for more information.
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>
46constexpr static int const SIZE = 400;
62struct Intersection final
69 , point{intersection_point}
71 , relative_angle{polar_angle - start_angle}
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);
91static std::vector<Geom::Point>
to_pixel_coordinate(std::vector<Geom::Point>
const &points,
double scale,
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>()} {
107 set_name(
"ColorWheel");
108 add_css_class(
"flat");
118 auto const click = Gtk::GestureClick::create();
119 click->set_button(0);
124 auto const motion = Gtk::EventControllerMotion::create();
125 motion->signal_motion().connect([
this, &motion = *motion](
auto &&...args) {
return _on_motion(motion, args...); });
128 auto const key = Gtk::EventControllerKey::create();
135 Gtk::AspectFrame(cobject),
136 _bin(
Gtk::make_managed<
Bin>()),
137 _values(type, initial_color),
138 _drawing_area{
Gtk::make_managed<
Gtk::DrawingArea>()} {
146 auto state = motion.get_current_event_state();
193 case GDK_KEY_KP_Down:
195 case GDK_KEY_KP_Left:
197 case GDK_KEY_KP_Right:
205 bool const ,
bool const emit)
224 auto const cx =
width / 2.0;
225 auto const cy =
height / 2.0;
227 auto const stride = Cairo::ImageSurface::format_stride_for_width(Cairo::Surface::Format::RGB24,
width);
231 auto const &[r_min, r_max] =
get_radii();
232 double r2_max = (r_max+2) * (r_max+2);
233 double r2_min = (r_min-2) * (r_min-2);
235 for (
int i = 0; i <
height; ++i) {
237 double dy = (cy - i);
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) {
245 double angle = atan2 (dy,
dx);
249 double hue = angle/(2.0 * M_PI);
250 *p++ =
Color(Type::HSV, {hue, 1.0, 1.0}).toARGB();
257 Cairo::Surface::Format::RGB24,
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;
284 constexpr int padding = 3;
288 auto const stride = Cairo::ImageSurface::format_stride_for_width(Cairo::Surface::Format::RGB24,
width);
291 for (
int y = 0; y <
height; ++y) {
292 if (p0.y <= y + padding && y - padding < p2.y) {
295 double y_inter = std::clamp(
static_cast<double>(y), p0.y, p2.y);
297 side0 =
lerp(p0, p1, p0.y, p1.y, y_inter);
299 side0 =
lerp(p1, p2, p1.y, p2.y, y_inter);
305 if (side0.
x > side1.
x) {
306 std::swap (side0, side1);
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);
314 for (; x <= x_start; ++x) {
317 for (; x < x_end; ++x) {
318 *p++ =
lerp(side0, side1, side0.
x, side1.
x, x).color.toARGB();
320 for (; x <
width; ++x) {
328 ColorPoint temp_point =
lerp(p0, p1, p0.x, p1.x, (p0.x + p1.x) / 2.0);
332 temp_point =
lerp(p0, p2, p0.x, p2.x, (p0.x + p2.x) / 2.0);
336 temp_point =
lerp(p1, p2, p1.x, p2.x, (p1.x + p2.x) / 2.0);
342 Cairo::Surface::Format::RGB24,
360 auto const cx =
width / 2.0;
361 auto const cy =
height / 2.0;
363 cr->set_antialias(Cairo::ANTIALIAS_SUBPIXEL);
368 auto const &[r_min, r_max] =
get_radii();
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);
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;
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));
393 cr->move_to(p0.x, p0.y);
394 cr->line_to(p1.x, p1.y);
395 cr->line_to(p2.x, p2.y);
403 cr->set_source_rgb(a, a, a);
404 cr->begin_new_path();
412 cr->set_line_width(1.0);
415 auto const rgba =
change_alpha(Widget::get_color(), 0.7);
416 Gdk::Cairo::set_source_rgba(cr, rgba);
417 cr->begin_new_path();
420 cr->set_source_rgb(1 - a, 1 - a, 1 - a);
421 cr->begin_new_path();
443 bool keep_focus =
true;
446 case Gtk::DirectionType::TAB_BACKWARD:
454 case Gtk::DirectionType::TAB_FORWARD:
468 double const cx =
width/2.0;
469 double const cy =
height/2.0;
471 double const r = std::min(cx, cy) * (1 -
_ring_width);
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;
482 double xt =
lerp(0.0, 1.0, -0.5, 1.0, xp);
483 xt = std::clamp(xt, 0.0, 1.0);
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);
507 auto const cx =
width / 2.0;
508 auto const cy =
height / 2.0;
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;
516 double r2 =
dx *
dx + dy * dy;
518 return (r2_min < r2 && r2 < r2_max);
524 auto const &[x0, y0] = p0.get_xy();
525 auto const &[x1, y1] = p1.get_xy();
526 auto const &[x2, y2] = p2.get_xy();
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;
532 double t = ((x2 - x1) * (y - y1) - (y2 - y1) * (x - x1)) /
det;
533 return (t >= 0.0 && s + t <= 1.0);
539 double cx =
width / 2.0;
542 double angle = -atan2(y - cy, x - cx);
555 int ,
double x,
double y)
563 return Gtk::EventSequenceState::CLAIMED;
570 return Gtk::EventSequenceState::CLAIMED;
573 return Gtk::EventSequenceState::NONE;
580 return Gtk::EventSequenceState::CLAIMED;
586 auto state = motion.get_current_event_state();
603 static constexpr double delta_hue = 2.0 /
MAX_HUE;
604 auto dx = 0.0, dy = 0.0;
613 case GDK_KEY_KP_Down:
618 case GDK_KEY_KP_Left:
623 case GDK_KEY_KP_Right:
627 if (
dx == 0.0 && dy == 0.0)
return false;
629 bool changed =
false;
653 auto &[r_min, r_max] = *
_radii;
665 double const cx =
width / 2.0;
666 double const cy =
height / 2.0;
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;
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})};
696 auto const &[x0, y0] = p0.get_xy();
697 auto const &[x1, y1] = p1.get_xy();
698 auto const &[x2, y2] = p2.get_xy();
703 mx = x1 + (x2 - x1) *
_values[2] + (x0 - x2) * v1v2;
704 my = y1 + (y2 - y1) *
_values[2] + (y0 - y2) * v1v2;
709 :
Glib::ObjectBase{
"ColorWheelHSL"}
723 _picker_geometry = std::make_unique<PickerGeometry>();
729 _picker_geometry = std::make_unique<PickerGeometry>();
733 bool ,
bool const emit)
762 double closest_distance = -1;
764 for (
auto const &line : lines) {
766 if (closest_distance < 0 || d < closest_distance) {
767 closest_distance = d;
768 closest_line = &line;
772 g_assert(closest_line);
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);
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]);
787 intersections.emplace_back(i, j, xings.front().point(), start_angle);
791 std::sort(intersections.begin(), intersections.end(), [](Intersection
const &lhs, Intersection
const &rhs) {
792 return lhs.relative_angle.radians0() >= rhs.relative_angle.radians0();
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];
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;
809 ordered_vertices.emplace_back(intersection.point);
810 circumradius = std::max(circumradius, intersection.point.length());
820 int const width = allocation.get_width();
821 int const height = allocation.get_height();
829 return {allocation.get_width(), allocation.get_height()};
834 return std::min(allocation.get_width(), allocation.get_height());
854 for (
auto &point : polygon_vertices_px) {
858 bool const is_vertex =
_vertex();
859 cr->set_antialias(Cairo::ANTIALIAS_SUBPIXEL);
869 auto it = polygon_vertices_px.begin();
871 for (++it; it != polygon_vertices_px.end(); ++it) {
884 cr->set_line_width(1);
886 cr->set_source_rgb(1.0, 1.0, 1.0);
887 cr->set_dash(dashes, 0.0);
888 cr->begin_new_path();
892 cr->set_source_rgb(0.0, 0.0, 0.0);
894 cr->begin_new_path();
901 cr->set_source_rgba(gray, gray, gray, alpha);
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();
912 cr->begin_new_path();
920 cr->set_line_width(inner_stroke_width);
921 cr->begin_new_path();
929 cr->set_source_rgb(1 - gray, 1 - gray, 1 - gray);
930 cr->begin_new_path();
939 int const width = allocation.get_width();
940 int const height = allocation.get_height();
973 for (
auto const &point : polygon_vertices_px) {
979 auto const bounding_max = bounding_rect.
max().ceil();
980 auto const bounding_min = bounding_rect.
min().floor();
982 int const stride = Cairo::ImageSurface::format_stride_for_width(Cairo::Surface::Format::RGB24,
_cache_size.
x());
986 std::vector<guint32> buffer_line(
stride / 4);
989 std::vector<double> color_vals = {
_values[2] * 100, 0, 0};
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++) {
996 color_vals[1] = point[
Geom::X];
997 color_vals[2] = point[
Geom::Y];
1002 p[i] = color.toARGB();
1010 std::memcpy(t, buffer_line.data(),
stride);
1019 int ,
double x,
double y)
1026 if (region.contains(event_pt.round())) {
1030 return Gtk::EventSequenceState::CLAIMED;
1033 return Gtk::EventSequenceState::NONE;
1039 return Gtk::EventSequenceState::CLAIMED;
1052 bool consumed =
false;
1057 double const marker_move = 1.0 /
_scale;
1062 luv.set(2, luv[2] + marker_move);
1066 case GDK_KEY_KP_Down:
1067 luv.set(2, luv[2] - marker_move);
1071 case GDK_KEY_KP_Left:
1072 luv.set(1, luv[1] - marker_move);
1076 case GDK_KEY_KP_Right:
1077 luv.set(1, luv[1] + marker_move);
1081 if (!consumed)
return false;
1101 , color(
std::move(
c))
1110static double lerp(
double v0,
double v1,
double t0,
double t1,
double t)
1112 double const s = (t0 != t1) ? (t - t0) / (t1 - t0) : 0.0;
1119 double x =
lerp(v0.
x, v1.
x, t0, t1, t);
1120 double y =
lerp(v0.
y, v1.
y, t0, t1, t);
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);
1135 return (
c[0] * 0.2125 +
c[1] * 0.7154 +
c[2] * 0.0721);
1175 double scale,
double resize)
1177 std::vector<Geom::Point>
result;
1179 for (
auto const &p : points) {
1203 double gradient = (p1.
y - p0.
y) / (p1.
x - p0.
x);
1204 if (std::abs(gradient) > 1.0) {
1208 double min_y = std::min(p0.
y, p1.
y);
1209 double max_y = std::max(p0.
y, p1.
y);
1211 double min_x = std::min(p0.
x, p1.
x);
1212 double max_x = std::max(p0.
x, p1.
x);
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,
1218 double end_x =
lerp(p0, p1, p0.
y, p1.
y, std::clamp(
static_cast<double>(y) + 1, min_y,
1220 if (start_x > end_x) {
1221 std::swap(start_x, end_x);
1225 p +=
static_cast<int>(start_x);
1226 for (
int x = start_x; x <= end_x; ++x) {
1228 ColorPoint point =
lerp(p0, p1, p0.
x, p1.
x, std::clamp(
static_cast<double>(x),
1232 if (pad_upwards && (point.
y -
offset) >= 0) {
Various trigoniometric helper functions.
Bin: widget that can hold one child, useful as a base class of custom widgets.
Wrapper for angular values.
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.
constexpr IntCoord x() const noexcept
constexpr IntCoord y() const noexcept
Infinite line on a plane.
Coord nearestTime(Point const &p) const
Find a point on the line closest to the query point.
Point pointAt(Coord t) const
Two-dimensional point that doubles as a vector.
Axis aligned, non-empty rectangle.
uint32_t toARGB(double opacity=1.0) const
Return the RGBA int32 as an ARGB format number.
bool set(unsigned int index, double value)
Set a specific channel in the color.
std::optional< Color > converted(Color const &other) const
Return a copy of this color converted to the same format as the other color.
void normalize()
Make sure the values for this color are within acceptable ranges.
double getOpacity() const
Get the opacity in this color, if it's stored.
std::shared_ptr< Space::AnySpace > const & getSpace() const
static std::array< Geom::Line, 6 > get_bounds(double l)
Calculate the bounds of the Luv colors in RGB gamut.
static std::vector< double > fromCoordinates(std::vector< double > const &in)
static std::vector< double > toCoordinates(std::vector< double > const &in)
Color item used in palettes and swatches UI.
static T det(T a, T b, T c, T d)
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
Integral and real coordinate types and some basic utilities.
constexpr Coord lerp(Coord t, Coord a, Coord b)
Numerically stable linear interpolation.
Angle distance(Angle const &a, Angle const &b)
A set of useful color modifying functions which do not fit as generic methods on the color class itse...
std::pair< double, double > get_contrasting_color(double l)
double perceptual_lightness(double l)
double lightness(Color color)
auto use_state(Slot &&slot)
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).
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.
static cairo_user_data_key_t key
Glib::RefPtr< Gtk::Builder > builder
Gdk::RGBA change_alpha(const Gdk::RGBA &color, double new_alpha)
Geom::IntPoint dimensions(const Cairo::RefPtr< Cairo::ImageSurface > &surface)