From 7d2db8b232499b2a5069cb09a41d8a7a05e35e89 Mon Sep 17 00:00:00 2001 From: Thomas Voss Date: Sat, 19 Nov 2022 18:02:11 +0100 Subject: [PATCH] Support HSL, HSV, CMYK, short options, minor I/O fix, and add a manual page (#12) * use getopt_long(3) and support short options Now instead of `--format`, `--no-fancy`, and `--help` you can also use `-f`, `-n`, and `-h`. Additionally instead of just being able to do `--format ` you can now do the standard `--format=`. * remove initial space when m_bFancyOutput is true * add the HSL format * add the HSV format * add a manual page * silence compiler warning * make all the default target, and add install rule * add the CMYK format * add new formats to the README --- Makefile | 7 ++++ README.md | 6 ++- doc/hyprpicker.1 | 85 +++++++++++++++++++++++++++++++++++++++++++ src/events/Events.cpp | 82 ++++++++++++++++++++++++++++++++++++++++- src/hyprpicker.hpp | 7 +++- src/includes.hpp | 1 + src/main.cpp | 72 +++++++++++++++++++++--------------- 7 files changed, 225 insertions(+), 35 deletions(-) create mode 100644 doc/hyprpicker.1 diff --git a/Makefile b/Makefile index 7ff7e70..e58eb0d 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,8 @@ PKGS = wlroots wayland-server CFLAGS += $(foreach p,$(PKGS),$(shell pkg-config --cflags $(p))) LDLIBS += $(foreach p,$(PKGS),$(shell pkg-config --libs $(p))) +default: all + wlr-layer-shell-unstable-v1-protocol.h: $(WAYLAND_SCANNER) client-header \ protocols/wlr-layer-shell-unstable-v1.xml $@ @@ -58,3 +60,8 @@ all: make clear make protocols make release + +install: + mkdir -p ${DESTDIR}${PREFIX}/bin ${DESTDIR}${PREFIX}/share/man/man1 + cp doc/hyprpicker.1 ${DESTDIR}${PREFIX}/share/man/man1 + cp build/hyprpicker ${DESTDIR}${PREFIX}/bin diff --git a/README.md b/README.md index e3e83b5..420c70a 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,11 @@ Launch it. Click. That's it. ## Options -`--format [fmt]` to specify the output format (`hex`, `rgb`) +`-f | --format=[fmt]` specifies the output format (`cmyk`, `hex`, `rgb`, `hsl`, `hsv`) -`--no-fancy` disables the "fancy" (aka. colored) outputting +`-n | --no-fancy` disables the "fancy" (aka. colored) outputting + +`-h | --help` prints a help message `--autocopy` automatically copies the output to the clipboard (requires [wl-clipboard](https://github.com/bugaevc/wl-clipboard)) diff --git a/doc/hyprpicker.1 b/doc/hyprpicker.1 new file mode 100644 index 0000000..db8c4d1 --- /dev/null +++ b/doc/hyprpicker.1 @@ -0,0 +1,85 @@ +.Dd $Mdocdate: November 14 2022 $ +.Dt HYPRPICKER 1 +.Os Linux +.Sh NAME +.Nm hyprpicker +.Nd wlroots-compatible wayland color picker +.Sh SYNOPSIS +.Nm +.Op Fl nh +.Op Fl f Ar fmt +.Sh DESCRIPTION +The +.Nm +utility is a color-picker with support for various output formats. +When +.Nm +is invoked the cursor is transformed into a magnifying lens, and clicking on any +pixel of the screen will print out that pixels color to the standard output. +The default output format is hexadecimal, but that can be configured with the +.Fl f +option. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl f Ns , Fl \-format Ns = Ns Ar fmt +Select the format to output the selected pixels color in. +The argument +.Ar fmt +is case-insensitive. +The available options are: +.Pp +.Bl -hang -compact +.It Ar cmyk +.Pq Dq C% M% Y% K% +.It Ar hex +.Pq Dq #RRGGBB +.It Ar rgb +.Pq Dq R G B +.It Ar hsl +.Pq Dq H S% L% +.It Ar hsv +.Pq Dq H S% V% +.El +.Pp +The default format is +.Ar hex . +.It Fl n Ns , Fl \-no\-fancy +Disable colored output. +Default behavior is to color the output in the same color as the selected pixel. +.It Fl h Ns , Fl \-help +Display a help message and exit successfully from the program. +.El +.Sh ENVIRONMENT +.Bl -tag -width NO_COLOR +.It Ev NO_COLOR +If set, disables colored output. +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Get a pixels color: +.Pp +.Dl $ hyprpicker +.Pp +Get a pixels color in HSL, wrapped in a CSS +.Fn hsl +function: +.Pp +.Dl $ hyprpicker -f hsl | sed 's/^/rgb(/; s/$/)/; y/ /,/' +.Sh SEE ALSO +.Xr hyprctl 1 , +.Xr hyprland 1 , +.Xr sed 1 +.Pp +.Lk https://github.com/hyprwm/hyprpicker "The Hyprpicker Sources" +.Sh AUTHORS +.An -nosplit +The +.Nm +utility was originally written by +.An Vaxerski Aq Lk https://github.com/vaxerski +and the manual page by +.An Thomas Voss Aq Mt mail@thomasvoss.com . +.Sh BUGS +.Lk https://github.com/hyprwm/hyprpicker/issues "The Hyprpicker Bug Tracker" diff --git a/src/events/Events.cpp b/src/events/Events.cpp index ae66dce..63b3542 100644 --- a/src/events/Events.cpp +++ b/src/events/Events.cpp @@ -118,6 +118,13 @@ void Events::handlePointerMotion(void *data, struct wl_pointer *wl_pointer, uint } void Events::handlePointerButton(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t button_state) { + auto fmax3 = [](float a, float b, float c) -> float { + return (a > b && a > c) ? a : (b > c) ? b : c; + }; + auto fmin3 = [](float a, float b, float c) -> float { + return (a < b && a < c) ? a : (b < c) ? b : c; + }; + // get the px and print it const auto SCALE = Vector2D{ g_pHyprpicker->m_pLastSurface->screenBuffer.pixelSize.x / (g_pHyprpicker->m_pLastSurface->buffers[0].pixelSize.x / g_pHyprpicker->m_pLastSurface->m_pMonitor->scale), @@ -129,6 +136,30 @@ void Events::handlePointerButton(void *data, struct wl_pointer *wl_pointer, uint const auto COL = g_pHyprpicker->getColorFromPixel(g_pHyprpicker->m_pLastSurface, CLICKPOS); switch (g_pHyprpicker->m_bSelectedOutputMode) { + case OUTPUT_CMYK: + { + // http://www.codeproject.com/KB/applications/xcmyk.aspx + + float r = 1 - COL.r / 255.0f, + g = 1 - COL.g / 255.0f, + b = 1 - COL.b / 255.0f; + float k = fmin3(r, g, b), + K = 1 - k; + float c = (r - k) / K, + m = (g - k) / K, + y = (b - k) / K; + + c = std::round(c * 100); + m = std::round(m * 100); + y = std::round(y * 100); + k = std::round(k * 100); + + if (g_pHyprpicker->m_bFancyOutput) + Debug::log(NONE, "\033[38;2;%i;%i;%im%g%% %g%% %g%% %g%%\033[0m", COL.r, COL.g, COL.b, c, m, y, k); + else + Debug::log(NONE, "%g%% %g%% %g%% %g%%", c, m, y, k); + break; + } case OUTPUT_HEX: { auto toHex = [](int i) -> std::string { @@ -143,7 +174,7 @@ void Events::handlePointerButton(void *data, struct wl_pointer *wl_pointer, uint }; if (g_pHyprpicker->m_bFancyOutput) - Debug::log(NONE, "\033[38;2;%i;%i;%im #%s%s%s\033[0m", COL.r, COL.g, COL.b, toHex(COL.r).c_str(), toHex(COL.g).c_str(), toHex(COL.b).c_str()); + Debug::log(NONE, "\033[38;2;%i;%i;%im#%s%s%s\033[0m", COL.r, COL.g, COL.b, toHex(COL.r).c_str(), toHex(COL.g).c_str(), toHex(COL.b).c_str()); else Debug::log(NONE, "#%s%s%s", toHex(COL.r).c_str(), toHex(COL.g).c_str(), toHex(COL.b).c_str()); @@ -154,7 +185,7 @@ void Events::handlePointerButton(void *data, struct wl_pointer *wl_pointer, uint case OUTPUT_RGB: { if (g_pHyprpicker->m_bFancyOutput) - Debug::log(NONE, "\033[38;2;%i;%i;%im %i %i %i\033[0m", COL.r, COL.g, COL.b, COL.r, COL.g, COL.b); + Debug::log(NONE, "\033[38;2;%i;%i;%im%i %i %i\033[0m", COL.r, COL.g, COL.b, COL.r, COL.g, COL.b); else Debug::log(NONE, "%i %i %i", COL.r, COL.g, COL.b); @@ -162,6 +193,53 @@ void Events::handlePointerButton(void *data, struct wl_pointer *wl_pointer, uint Clipboard::copy("%i %i %i", COL.r, COL.g, COL.b); break; } + case OUTPUT_HSL: + case OUTPUT_HSV: + { + // https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB + + auto floatEq = [](float a, float b) -> bool { + return std::nextafter(a, std::numeric_limits::lowest()) <= b && std::nextafter(a, std::numeric_limits::max()) >= b; + }; + + float h, s, l, v; + float r = COL.r / 255.0f, + g = COL.g / 255.0f, + b = COL.b / 255.0f; + float max = fmax3(r, g, b), + min = fmin3(r, g, b); + float c = max - min; + + v = max; + if (c == 0) + h = 0; + else if (v == r) + h = 60 * (0 + (g - b) / c); + else if (v == g) + h = 60 * (2 + (b - r) / c); + else /* v == b */ + h = 60 * (4 + (r - g) / c); + + float l_or_v; + if (g_pHyprpicker->m_bSelectedOutputMode == OUTPUT_HSL) { + l = (max + min) / 2; + s = (floatEq(l, 0.0f) || floatEq(l, 1.0f)) ? 0 : (v - l) / std::min(l, 1 - l); + l_or_v = std::round(l * 100); + } else { + v = max; + s = floatEq(v, 0.0f) ? 0 : c / v; + l_or_v = std::round(v * 100); + } + + h = std::round(h); + s = std::round(s * 100); + + if (g_pHyprpicker->m_bFancyOutput) + Debug::log(NONE, "\033[38;2;%i;%i;%im%g %g%% %g%%\033[0m", COL.r, COL.g, COL.b, h, s, l_or_v); + else + Debug::log(NONE, "%g %g%% %g%%", h, s, l_or_v); + break; + } } g_pHyprpicker->finish(); diff --git a/src/hyprpicker.hpp b/src/hyprpicker.hpp index f55eddf..9bbb002 100644 --- a/src/hyprpicker.hpp +++ b/src/hyprpicker.hpp @@ -5,8 +5,11 @@ #include "helpers/PoolBuffer.hpp" enum eOutputMode { - OUTPUT_HEX = 0, - OUTPUT_RGB + OUTPUT_CMYK = 0, + OUTPUT_HEX, + OUTPUT_RGB, + OUTPUT_HSL, + OUTPUT_HSV }; class CHyprpicker { diff --git a/src/includes.hpp b/src/includes.hpp index 79f4d1d..4dff159 100644 --- a/src/includes.hpp +++ b/src/includes.hpp @@ -33,6 +33,7 @@ extern "C" { #include #include #include +#include #include #include #include diff --git a/src/main.cpp b/src/main.cpp index 1d811cf..8afc7d0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,43 +1,57 @@ +#include #include #include "hyprpicker.hpp" +static void help(void) { + std::cout << "Hyprpicker usage: hyprpicker [arg [...]].\n\nArguments:\n" << + " -f | --format=fmt | Specifies the output format (cmyk, hex, rgb, hsl, hsv)\n" << + " -n | --no-fancy | Disables the \"fancy\" (aka. colored) outputting\n" << + " -h | --help | Show this help message\n"; +} int main(int argc, char** argv, char** envp) { g_pHyprpicker = std::make_unique(); - // parse args - // 1 - format - int currentlyParsing = 0; - for (int i = 1; i < argc; i++) { - std::string arg = argv[i]; + while (true) { + int option_index = 0; + static struct option long_options[] = { + {"format", required_argument, NULL, 'f'}, + {"help", no_argument, NULL, 'h'}, + {"no-fancy", no_argument, NULL, 'n'}, + {NULL, 0, NULL, 0 } + }; - if (currentlyParsing == 0) { - if (arg == "--format") { - currentlyParsing = 1; - continue; - } else if (arg == "--no-fancy") { + int c = getopt_long(argc, argv, ":f:hn", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'f': + if (strcasecmp(optarg, "cmyk") == 0) + g_pHyprpicker->m_bSelectedOutputMode = OUTPUT_CMYK; + else if (strcasecmp(optarg, "hex") == 0) + g_pHyprpicker->m_bSelectedOutputMode = OUTPUT_HEX; + else if (strcasecmp(optarg, "rgb") == 0) + g_pHyprpicker->m_bSelectedOutputMode = OUTPUT_RGB; + else if (strcasecmp(optarg, "hsl") == 0) + g_pHyprpicker->m_bSelectedOutputMode = OUTPUT_HSL; + else if (strcasecmp(optarg, "hsv") == 0) + g_pHyprpicker->m_bSelectedOutputMode = OUTPUT_HSV; + else { + Debug::log(NONE, "Unrecognized format %s", optarg); + exit(1); + } + break; + case 'h': + help(); + exit(0); + case 'n': g_pHyprpicker->m_bFancyOutput = false; - } else if (arg == "--autocopy") { - g_pHyprpicker->m_bAutoCopy = true; - } else { - std::cout << "Hyprpicker usage: hyprpicker [arg [...]].\n\nArguments:\n" << - " --format [fmt] | Specifies the output format (hex, rgb)\n" << - " --no-fancy | Disables the \"fancy\" (aka. colored) outputting\n" << - " --autocopy | Automatically copies the output to the clipboard (requires wl-clipboard)\n" << - " --help | Show this help message\n"; + break; + default: + help(); exit(1); } - } else if (currentlyParsing == 1) { - if (arg == "hex") g_pHyprpicker->m_bSelectedOutputMode = OUTPUT_HEX; - else if (arg == "rgb") g_pHyprpicker->m_bSelectedOutputMode = OUTPUT_RGB; - else { - Debug::log(NONE, "Unrecognized format %s", arg.c_str()); - exit(1); - } - - currentlyParsing = 0; - continue; - } } if (!isatty(fileno(stdout)) || getenv("NO_COLOR"))