GHOST/Wayland: invert cursor color, pre-multiply alpha
- Cursors in wayland are expected to use pre-multiplied alpha. Note that cursor generators create straight alpha, pre multiplier in the wayland backend. - Invert cursor colors on Wayland since dark cursors are often default, this could use dark theme settings in the future.
This commit is contained in:
@@ -334,7 +334,7 @@ extern GHOST_TSuccess GHOST_HasCursorShape(GHOST_WindowHandle windowhandle,
|
||||
* \param mask: The mask for 1bpp cursor, nullptr if RGBA cursor.
|
||||
* \param size: The width & height of the cursor.
|
||||
* \param hot_spot: The X,Y coordinates of the cursor hot-spot.
|
||||
* \param can_invert_color: Let macOS invert cursor color to match platform convention.
|
||||
* \param can_invert_color: Let the cursor colors be inverted to match platform convention.
|
||||
* \return Indication of success.
|
||||
*/
|
||||
extern GHOST_TSuccess GHOST_SetCustomCursorShape(GHOST_WindowHandle windowhandle,
|
||||
|
||||
@@ -86,15 +86,19 @@ typedef struct GHOST_CursorGenerator {
|
||||
* The generator must guarantee the resulting size (dimensions written to `r_bitmap_size`)
|
||||
* never exceeds `cursor_size_max`.
|
||||
* \param r_hot_spot: The cursor hot-spot.
|
||||
* \param r_can_invert_color: When true, the call it can be inverted too much dark themes.
|
||||
*
|
||||
* \return the bitmap data or null if it could not be generated.
|
||||
* Must be at least: `sizeof(uint8_t[4]) * r_bitmap_size[0] * r_bitmap_size[1]` allocated bytes.
|
||||
* - The color is "straight" (alpha is not pre-multiplied).
|
||||
* - At least: `sizeof(uint8_t[4]) * r_bitmap_size[0] * r_bitmap_size[1]` allocated bytes.
|
||||
*/
|
||||
uint8_t *(*generate_fn)(const struct GHOST_CursorGenerator *cursor_generator,
|
||||
int cursor_size,
|
||||
int cursor_size_max,
|
||||
uint8_t *(*alloc_fn)(size_t size),
|
||||
int r_bitmap_size[2],
|
||||
int r_hot_spot[2]);
|
||||
int r_hot_spot[2],
|
||||
bool *r_can_invert_color);
|
||||
/**
|
||||
* Called once GHOST has finished with this object,
|
||||
* Typically this would free `user_data`.
|
||||
|
||||
@@ -481,6 +481,10 @@ struct GWL_Cursor {
|
||||
* See #update_cursor_scale.
|
||||
*/
|
||||
int theme_size = 0;
|
||||
/**
|
||||
* Prefer dark theme cursors where possible.
|
||||
*/
|
||||
bool use_dark_theme = false;
|
||||
};
|
||||
|
||||
/** \} */
|
||||
@@ -1913,6 +1917,26 @@ static uint32_t round_up_uint(const uint32_t x, const uint32_t multiple)
|
||||
return ((x + multiple - 1) / multiple) * multiple;
|
||||
}
|
||||
|
||||
static uint32_t rgba_straight_to_premul(uint32_t rgba_uint)
|
||||
{
|
||||
uint8_t *rgba = reinterpret_cast<uint8_t *>(&rgba_uint);
|
||||
const uint32_t alpha = uint32_t(rgba[3]);
|
||||
rgba[0] = uint8_t(((alpha * rgba[0]) + (0xff / 2)) / 0xff);
|
||||
rgba[1] = uint8_t(((alpha * rgba[1]) + (0xff / 2)) / 0xff);
|
||||
rgba[2] = uint8_t(((alpha * rgba[2]) + (0xff / 2)) / 0xff);
|
||||
return rgba_uint;
|
||||
}
|
||||
|
||||
static uint32_t rgba_straight_to_premul_inverted(uint32_t rgba_uint)
|
||||
{
|
||||
uint8_t *rgba = reinterpret_cast<uint8_t *>(&rgba_uint);
|
||||
const uint32_t alpha = uint32_t(rgba[3]);
|
||||
rgba[0] = uint8_t(((alpha * (0xff - rgba[0])) + (0xff / 2)) / 0xff);
|
||||
rgba[1] = uint8_t(((alpha * (0xff - rgba[1])) + (0xff / 2)) / 0xff);
|
||||
rgba[2] = uint8_t(((alpha * (0xff - rgba[2])) + (0xff / 2)) / 0xff);
|
||||
return rgba_uint;
|
||||
}
|
||||
|
||||
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
|
||||
static const char *strchr_or_end(const char *str, const char ch)
|
||||
{
|
||||
@@ -6066,6 +6090,10 @@ static void gwl_seat_capability_pointer_enable(GWL_Seat *seat)
|
||||
seat->cursor.theme_size = int(value);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: detect this from the system.
|
||||
* We *could* have weak support based on checking for known themes. */
|
||||
seat->cursor.use_dark_theme = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8622,6 +8650,7 @@ static wl_buffer *ghost_wl_buffer_from_cursor_generator(const GHOST_CursorGenera
|
||||
size_t *buffer_data_size_p,
|
||||
const int cursor_size,
|
||||
const int cursor_size_max,
|
||||
const int use_dark_theme,
|
||||
const int scale,
|
||||
int r_bitmap_size[2],
|
||||
int r_hot_spot[2])
|
||||
@@ -8634,6 +8663,7 @@ static wl_buffer *ghost_wl_buffer_from_cursor_generator(const GHOST_CursorGenera
|
||||
|
||||
int bitmap_size_src[2];
|
||||
int hot_spot[2];
|
||||
bool can_invert_color = false;
|
||||
|
||||
uint8_t *bitmap_src = cg.generate_fn(
|
||||
&cg,
|
||||
@@ -8641,12 +8671,15 @@ static wl_buffer *ghost_wl_buffer_from_cursor_generator(const GHOST_CursorGenera
|
||||
cursor_size_max,
|
||||
[](size_t size) -> uint8_t * { return new uint8_t[size]; },
|
||||
bitmap_size_src,
|
||||
hot_spot);
|
||||
hot_spot,
|
||||
&can_invert_color);
|
||||
|
||||
if (bitmap_src == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const bool invert_color = can_invert_color && use_dark_theme;
|
||||
|
||||
/* There is no need to adjust the hot-spot when resizing. */
|
||||
int bitmap_size_dst[2] = {
|
||||
int(round_up_uint(bitmap_size_src[0], scale)),
|
||||
@@ -8662,12 +8695,21 @@ static wl_buffer *ghost_wl_buffer_from_cursor_generator(const GHOST_CursorGenera
|
||||
/* NOTE: the copy could be skipped in trivial cases.
|
||||
* Since it's such a small amount of data it hardly seems worth it. */
|
||||
if (is_trivial_copy) {
|
||||
/* RGBA color, direct copy. */
|
||||
/* RGBA color. */
|
||||
const uint32_t *px_src = reinterpret_cast<const uint32_t *>(bitmap_src);
|
||||
uint32_t *px_dst = static_cast<uint32_t *>(*buffer_data_p);
|
||||
for (int y = 0; y < bitmap_size_src[1]; y++) {
|
||||
for (int x = 0; x < bitmap_size_src[0]; x++) {
|
||||
*px_dst++ = *px_src++;
|
||||
if (invert_color) {
|
||||
for (int y = 0; y < bitmap_size_src[1]; y++) {
|
||||
for (int x = 0; x < bitmap_size_src[0]; x++) {
|
||||
*px_dst++ = rgba_straight_to_premul_inverted(*px_src++);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int y = 0; y < bitmap_size_src[1]; y++) {
|
||||
for (int x = 0; x < bitmap_size_src[0]; x++) {
|
||||
*px_dst++ = rgba_straight_to_premul(*px_src++);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8675,13 +8717,21 @@ static wl_buffer *ghost_wl_buffer_from_cursor_generator(const GHOST_CursorGenera
|
||||
/* RGBA color, copy into an expanded buffer. */
|
||||
const uint32_t *px_src = reinterpret_cast<const uint32_t *>(bitmap_src);
|
||||
uint32_t *px_dst = static_cast<uint32_t *>(*buffer_data_p);
|
||||
for (int y = 0; y < bitmap_size_dst[1]; y++) {
|
||||
for (int x = 0; x < bitmap_size_dst[0]; x++) {
|
||||
if (x >= bitmap_size_src[0] || y >= bitmap_size_src[1]) {
|
||||
*px_dst++ = 0x0;
|
||||
if (invert_color) {
|
||||
for (int y = 0; y < bitmap_size_dst[1]; y++) {
|
||||
for (int x = 0; x < bitmap_size_dst[0]; x++) {
|
||||
*px_dst++ = (x >= bitmap_size_src[0] || y >= bitmap_size_src[1]) ?
|
||||
0x0 :
|
||||
rgba_straight_to_premul_inverted(*px_src++);
|
||||
}
|
||||
else {
|
||||
*px_dst++ = *px_src++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int y = 0; y < bitmap_size_dst[1]; y++) {
|
||||
for (int x = 0; x < bitmap_size_dst[0]; x++) {
|
||||
*px_dst++ = (x >= bitmap_size_src[0] || y >= bitmap_size_src[1]) ?
|
||||
0x0 :
|
||||
rgba_straight_to_premul(*px_src++);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8744,6 +8794,7 @@ GHOST_TSuccess GHOST_SystemWayland::cursor_shape_custom_set(const GHOST_CursorGe
|
||||
&cursor.custom_data_size,
|
||||
cursor_size,
|
||||
cursor_size_max,
|
||||
cursor.use_dark_theme,
|
||||
scale,
|
||||
bitmap_size,
|
||||
hot_spot);
|
||||
|
||||
@@ -63,6 +63,9 @@ struct BCursor {
|
||||
* A factor (0-1) from the top-left corner of the image (not of the document size).
|
||||
*/
|
||||
blender::float2 hotspot;
|
||||
/**
|
||||
* By default cursors are "light", allow dark themes to invert.
|
||||
*/
|
||||
bool can_invert;
|
||||
};
|
||||
|
||||
@@ -172,7 +175,7 @@ static int wm_cursor_size(const wmWindow *win)
|
||||
}
|
||||
|
||||
/**
|
||||
* Flip and RGBA byte buffer in-place.
|
||||
* Flip an RGBA byte buffer in-place.
|
||||
*/
|
||||
static void cursor_bitmap_rgba_flip_y(uint8_t *buffer, const size_t size[2])
|
||||
{
|
||||
@@ -286,7 +289,8 @@ static bool window_set_custom_cursor_generator(wmWindow *win, const BCursor &cur
|
||||
const int cursor_size_max,
|
||||
uint8_t *(*alloc_fn)(size_t size),
|
||||
int r_bitmap_size[2],
|
||||
int r_hot_spot[2]) -> uint8_t * {
|
||||
int r_hot_spot[2],
|
||||
bool *r_can_invert_color) -> uint8_t * {
|
||||
const BCursor &cursor = *(const BCursor *)(cursor_generator->user_data);
|
||||
/* Currently SVG uses the `cursor_size` as the maximum. */
|
||||
UNUSED_VARS(cursor_size_max);
|
||||
@@ -305,6 +309,8 @@ static bool window_set_custom_cursor_generator(wmWindow *win, const BCursor &cur
|
||||
r_hot_spot[0] = int(cursor.hotspot[0] * (bitmap_size[0] - 1));
|
||||
r_hot_spot[1] = int(cursor.hotspot[1] * (bitmap_size[1] - 1));
|
||||
|
||||
*r_can_invert_color = cursor.can_invert;
|
||||
|
||||
return bitmap_rgba;
|
||||
};
|
||||
|
||||
@@ -805,7 +811,8 @@ static bool wm_cursor_text_generator(wmWindow *win, const std::string &text, int
|
||||
const int cursor_size_max,
|
||||
uint8_t *(*alloc_fn)(size_t size),
|
||||
int r_bitmap_size[2],
|
||||
int r_hot_spot[2]) -> uint8_t * {
|
||||
int r_hot_spot[2],
|
||||
bool *r_can_invert_color) -> uint8_t * {
|
||||
const WMCursorText &cursor_text = *(const WMCursorText *)(cursor_generator->user_data);
|
||||
|
||||
int bitmap_size[2];
|
||||
@@ -826,6 +833,9 @@ static bool wm_cursor_text_generator(wmWindow *win, const std::string &text, int
|
||||
r_hot_spot[0] = bitmap_size[0] / 2;
|
||||
r_hot_spot[1] = bitmap_size[1] / 2;
|
||||
|
||||
/* Always use a dark background, not optional. */
|
||||
*r_can_invert_color = false;
|
||||
|
||||
return bitmap_rgba;
|
||||
};
|
||||
|
||||
@@ -877,7 +887,8 @@ static bool wm_cursor_text_pixmap(wmWindow *win, const std::string &text, int fo
|
||||
nullptr,
|
||||
bitmap_size,
|
||||
hot_spot,
|
||||
true);
|
||||
/* Always use a black background. */
|
||||
false);
|
||||
MEM_freeN(bitmap_rgba);
|
||||
|
||||
return (success == GHOST_kSuccess) ? true : false;
|
||||
|
||||
Reference in New Issue
Block a user