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 <fmt>` you can now do the standard `--format=<fmt>`.

* 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
This commit is contained in:
Thomas Voss 2022-11-19 18:02:11 +01:00 committed by GitHub
parent a82b20e979
commit 7d2db8b232
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 225 additions and 35 deletions

View file

@ -10,6 +10,8 @@ PKGS = wlroots wayland-server
CFLAGS += $(foreach p,$(PKGS),$(shell pkg-config --cflags $(p))) CFLAGS += $(foreach p,$(PKGS),$(shell pkg-config --cflags $(p)))
LDLIBS += $(foreach p,$(PKGS),$(shell pkg-config --libs $(p))) LDLIBS += $(foreach p,$(PKGS),$(shell pkg-config --libs $(p)))
default: all
wlr-layer-shell-unstable-v1-protocol.h: wlr-layer-shell-unstable-v1-protocol.h:
$(WAYLAND_SCANNER) client-header \ $(WAYLAND_SCANNER) client-header \
protocols/wlr-layer-shell-unstable-v1.xml $@ protocols/wlr-layer-shell-unstable-v1.xml $@
@ -58,3 +60,8 @@ all:
make clear make clear
make protocols make protocols
make release 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

View file

@ -10,9 +10,11 @@ Launch it. Click. That's it.
## Options ## 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)) `--autocopy` automatically copies the output to the clipboard (requires [wl-clipboard](https://github.com/bugaevc/wl-clipboard))

85
doc/hyprpicker.1 Normal file
View file

@ -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"

View file

@ -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) { 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 // get the px and print it
const auto SCALE = Vector2D{ 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), 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); const auto COL = g_pHyprpicker->getColorFromPixel(g_pHyprpicker->m_pLastSurface, CLICKPOS);
switch (g_pHyprpicker->m_bSelectedOutputMode) { 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: case OUTPUT_HEX:
{ {
auto toHex = [](int i) -> std::string { 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) 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 else
Debug::log(NONE, "#%s%s%s", toHex(COL.r).c_str(), toHex(COL.g).c_str(), toHex(COL.b).c_str()); 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: case OUTPUT_RGB:
{ {
if (g_pHyprpicker->m_bFancyOutput) 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 else
Debug::log(NONE, "%i %i %i", COL.r, COL.g, COL.b); 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); Clipboard::copy("%i %i %i", COL.r, COL.g, COL.b);
break; 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<double>::lowest()) <= b && std::nextafter(a, std::numeric_limits<double>::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(); g_pHyprpicker->finish();

View file

@ -5,8 +5,11 @@
#include "helpers/PoolBuffer.hpp" #include "helpers/PoolBuffer.hpp"
enum eOutputMode { enum eOutputMode {
OUTPUT_HEX = 0, OUTPUT_CMYK = 0,
OUTPUT_RGB OUTPUT_HEX,
OUTPUT_RGB,
OUTPUT_HSL,
OUTPUT_HSV
}; };
class CHyprpicker { class CHyprpicker {

View file

@ -33,6 +33,7 @@ extern "C" {
#include <cairo.h> #include <cairo.h>
#include <cairo/cairo.h> #include <cairo/cairo.h>
#include <fcntl.h> #include <fcntl.h>
#include <getopt.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>

View file

@ -1,42 +1,56 @@
#include <strings.h>
#include <iostream> #include <iostream>
#include "hyprpicker.hpp" #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) { int main(int argc, char** argv, char** envp) {
g_pHyprpicker = std::make_unique<CHyprpicker>(); g_pHyprpicker = std::make_unique<CHyprpicker>();
// parse args while (true) {
// 1 - format int option_index = 0;
int currentlyParsing = 0; static struct option long_options[] = {
for (int i = 1; i < argc; i++) { {"format", required_argument, NULL, 'f'},
std::string arg = argv[i]; {"help", no_argument, NULL, 'h'},
{"no-fancy", no_argument, NULL, 'n'},
{NULL, 0, NULL, 0 }
};
if (currentlyParsing == 0) { int c = getopt_long(argc, argv, ":f:hn", long_options, &option_index);
if (arg == "--format") { if (c == -1)
currentlyParsing = 1; break;
continue;
} else if (arg == "--no-fancy") { switch (c) {
g_pHyprpicker->m_bFancyOutput = false; case 'f':
} else if (arg == "--autocopy") { if (strcasecmp(optarg, "cmyk") == 0)
g_pHyprpicker->m_bAutoCopy = true; g_pHyprpicker->m_bSelectedOutputMode = OUTPUT_CMYK;
} else { else if (strcasecmp(optarg, "hex") == 0)
std::cout << "Hyprpicker usage: hyprpicker [arg [...]].\n\nArguments:\n" << g_pHyprpicker->m_bSelectedOutputMode = OUTPUT_HEX;
" --format [fmt] | Specifies the output format (hex, rgb)\n" << else if (strcasecmp(optarg, "rgb") == 0)
" --no-fancy | Disables the \"fancy\" (aka. colored) outputting\n" << g_pHyprpicker->m_bSelectedOutputMode = OUTPUT_RGB;
" --autocopy | Automatically copies the output to the clipboard (requires wl-clipboard)\n" << else if (strcasecmp(optarg, "hsl") == 0)
" --help | Show this help message\n"; g_pHyprpicker->m_bSelectedOutputMode = OUTPUT_HSL;
exit(1); else if (strcasecmp(optarg, "hsv") == 0)
} g_pHyprpicker->m_bSelectedOutputMode = OUTPUT_HSV;
} else if (currentlyParsing == 1) {
if (arg == "hex") g_pHyprpicker->m_bSelectedOutputMode = OUTPUT_HEX;
else if (arg == "rgb") g_pHyprpicker->m_bSelectedOutputMode = OUTPUT_RGB;
else { else {
Debug::log(NONE, "Unrecognized format %s", arg.c_str()); Debug::log(NONE, "Unrecognized format %s", optarg);
exit(1); exit(1);
} }
break;
currentlyParsing = 0; case 'h':
continue; help();
exit(0);
case 'n':
g_pHyprpicker->m_bFancyOutput = false;
break;
default:
help();
exit(1);
} }
} }