From 5741a5d433cd6555b608b9488a3b6cad1de30800 Mon Sep 17 00:00:00 2001 From: Harley Acheson Date: Wed, 16 Aug 2023 01:14:36 +0200 Subject: [PATCH] UI: Allow Eyedropper Outside of Blender This adds a new Ghost function, GHOST_GetPixelAtCursor, that allows picking colors from outside of Blender windows. This only has an implementation for the Windows platform, but this should allow other platforms to also do so if possible. Pull Request: https://projects.blender.org/blender/blender/pulls/105324 --- intern/ghost/GHOST_C-api.h | 7 +++ intern/ghost/GHOST_ISystem.hh | 7 +++ intern/ghost/GHOST_Types.h | 7 ++- intern/ghost/intern/GHOST_C-api.cc | 6 +++ intern/ghost/intern/GHOST_System.cc | 5 ++ intern/ghost/intern/GHOST_System.hh | 7 +++ intern/ghost/intern/GHOST_SystemCocoa.mm | 2 + intern/ghost/intern/GHOST_SystemHeadless.hh | 1 + intern/ghost/intern/GHOST_SystemSDL.cc | 2 + intern/ghost/intern/GHOST_SystemWayland.cc | 2 + intern/ghost/intern/GHOST_SystemWin32.cc | 25 +++++++++ intern/ghost/intern/GHOST_SystemWin32.hh | 7 +++ intern/ghost/intern/GHOST_SystemX11.cc | 2 + .../interface/eyedroppers/eyedropper_color.cc | 51 ++++++++++--------- source/blender/windowmanager/WM_api.hh | 8 +++ .../blender/windowmanager/intern/wm_draw.cc | 5 ++ .../blender/windowmanager/intern/wm_window.cc | 3 ++ 17 files changed, 121 insertions(+), 26 deletions(-) diff --git a/intern/ghost/GHOST_C-api.h b/intern/ghost/GHOST_C-api.h index 412a6d961f3..028fa2cc263 100644 --- a/intern/ghost/GHOST_C-api.h +++ b/intern/ghost/GHOST_C-api.h @@ -764,6 +764,13 @@ extern void GHOST_SetMultitouchGestures(GHOST_SystemHandle systemhandle, const b */ extern void GHOST_SetTabletAPI(GHOST_SystemHandle systemhandle, GHOST_TTabletAPI api); +/** + * Get the color of the pixel at the current mouse cursor location + * \param r_color: returned sRGB float colors + * \return Success value (true == successful and supported by platform) + */ +extern GHOST_TSuccess GHOST_GetPixelAtCursor(float r_color[3]); + /** * Access to rectangle width. * \param rectanglehandle: The handle to the rectangle. diff --git a/intern/ghost/GHOST_ISystem.hh b/intern/ghost/GHOST_ISystem.hh index ef4495096d2..0e145c9eac6 100644 --- a/intern/ghost/GHOST_ISystem.hh +++ b/intern/ghost/GHOST_ISystem.hh @@ -441,6 +441,13 @@ class GHOST_ISystem { */ virtual void setTabletAPI(GHOST_TTabletAPI api) = 0; + /** + * Get the color of the pixel at the current mouse cursor location + * \param r_color: returned sRGB float colors + * \return Success value (true == successful and supported by platform) + */ + virtual GHOST_TSuccess getPixelAtCursor(float r_color[3]) const = 0; + #ifdef WITH_INPUT_NDOF /** * Sets 3D mouse deadzone diff --git a/intern/ghost/GHOST_Types.h b/intern/ghost/GHOST_Types.h index 356ff0674b2..0bc79b2ac5c 100644 --- a/intern/ghost/GHOST_Types.h +++ b/intern/ghost/GHOST_Types.h @@ -107,6 +107,10 @@ typedef enum { * Set when there is support for system clipboard copy/paste. */ GHOST_kCapabilityClipboardImages = (1 << 4), + /** + * Support for sampling a color outside of the Blender windows. + */ + GHOST_kCapabilityDesktopSample = (1 << 5), } GHOST_TCapabilityFlag; /** @@ -115,7 +119,8 @@ typedef enum { */ #define GHOST_CAPABILITY_FLAG_ALL \ (GHOST_kCapabilityCursorWarp | GHOST_kCapabilityWindowPosition | \ - GHOST_kCapabilityPrimaryClipboard | GHOST_kCapabilityGPUReadFrontBuffer) + GHOST_kCapabilityPrimaryClipboard | GHOST_kCapabilityGPUReadFrontBuffer | \ + GHOST_kCapabilityClipboardImages | GHOST_kCapabilityDesktopSample) /* Xtilt and Ytilt represent how much the pen is tilted away from * vertically upright in either the X or Y direction, with X and Y the diff --git a/intern/ghost/intern/GHOST_C-api.cc b/intern/ghost/intern/GHOST_C-api.cc index 22c17c624e1..1e5599bfa0c 100644 --- a/intern/ghost/intern/GHOST_C-api.cc +++ b/intern/ghost/intern/GHOST_C-api.cc @@ -768,6 +768,12 @@ void GHOST_SetTabletAPI(GHOST_SystemHandle systemhandle, GHOST_TTabletAPI api) system->setTabletAPI(api); } +GHOST_TSuccess GHOST_GetPixelAtCursor(float r_color[3]) +{ + GHOST_ISystem *system = GHOST_ISystem::getSystem(); + return system->getPixelAtCursor(r_color); +} + int32_t GHOST_GetWidthRectangle(GHOST_RectangleHandle rectanglehandle) { return ((GHOST_Rect *)rectanglehandle)->getWidth(); diff --git a/intern/ghost/intern/GHOST_System.cc b/intern/ghost/intern/GHOST_System.cc index f86867df50c..65d1323938b 100644 --- a/intern/ghost/intern/GHOST_System.cc +++ b/intern/ghost/intern/GHOST_System.cc @@ -341,6 +341,11 @@ GHOST_TTabletAPI GHOST_System::getTabletAPI() return m_tabletAPI; } +GHOST_TSuccess GHOST_System::getPixelAtCursor(float[3] /* r_color */) const +{ + return GHOST_kFailure; +} + #ifdef WITH_INPUT_NDOF void GHOST_System::setNDOFDeadZone(float deadzone) { diff --git a/intern/ghost/intern/GHOST_System.hh b/intern/ghost/intern/GHOST_System.hh index bba17c44bb0..bb4688f865f 100644 --- a/intern/ghost/intern/GHOST_System.hh +++ b/intern/ghost/intern/GHOST_System.hh @@ -256,6 +256,13 @@ class GHOST_System : public GHOST_ISystem { virtual void setTabletAPI(GHOST_TTabletAPI api); GHOST_TTabletAPI getTabletAPI(void); + /** + * Get the color of the pixel at the current mouse cursor location + * \param r_color: returned sRGB float colors + * \return Success value (true == successful and supported by platform) + */ + GHOST_TSuccess getPixelAtCursor(float r_color[3]) const; + #ifdef WITH_INPUT_NDOF /*************************************************************************************** * Access to 3D mouse. diff --git a/intern/ghost/intern/GHOST_SystemCocoa.mm b/intern/ghost/intern/GHOST_SystemCocoa.mm index bf3e2e435bc..b2fa4de47b9 100644 --- a/intern/ghost/intern/GHOST_SystemCocoa.mm +++ b/intern/ghost/intern/GHOST_SystemCocoa.mm @@ -921,6 +921,8 @@ GHOST_TCapabilityFlag GHOST_SystemCocoa::getCapabilities() const ~( /* Cocoa has no support for a primary selection clipboard. */ GHOST_kCapabilityPrimaryClipboard | + /* Cocoa has no support for sampling colors from the desktop. */ + GHOST_kCapabilityDesktopSample | /* This Cocoa back-end has not yet implemented image copy/paste. */ GHOST_kCapabilityClipboardImages)); } diff --git a/intern/ghost/intern/GHOST_SystemHeadless.hh b/intern/ghost/intern/GHOST_SystemHeadless.hh index 8cd3828595d..59406ca44f2 100644 --- a/intern/ghost/intern/GHOST_SystemHeadless.hh +++ b/intern/ghost/intern/GHOST_SystemHeadless.hh @@ -50,6 +50,7 @@ class GHOST_SystemHeadless : public GHOST_System { /* No windowing functionality supported. */ ~(GHOST_kCapabilityWindowPosition | GHOST_kCapabilityCursorWarp | GHOST_kCapabilityPrimaryClipboard | + GHOST_kCapabilityDesktopSample | GHOST_kCapabilityClipboardImages)); } char *getClipboard(bool /*selection*/) const override diff --git a/intern/ghost/intern/GHOST_SystemSDL.cc b/intern/ghost/intern/GHOST_SystemSDL.cc index e54b7e27c54..ad6efef1a1c 100644 --- a/intern/ghost/intern/GHOST_SystemSDL.cc +++ b/intern/ghost/intern/GHOST_SystemSDL.cc @@ -772,6 +772,8 @@ GHOST_TCapabilityFlag GHOST_SystemSDL::getCapabilities() const ~( /* This SDL back-end has not yet implemented primary clipboard. */ GHOST_kCapabilityPrimaryClipboard | + /* This SDL back-end has not yet implemented color sampling the desktop. */ + GHOST_kCapabilityDesktopSample | /* This SDL back-end has not yet implemented image copy/paste. */ GHOST_kCapabilityClipboardImages)); } diff --git a/intern/ghost/intern/GHOST_SystemWayland.cc b/intern/ghost/intern/GHOST_SystemWayland.cc index b3d194c5856..e08049cb0ba 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cc +++ b/intern/ghost/intern/GHOST_SystemWayland.cc @@ -6843,6 +6843,8 @@ GHOST_TCapabilityFlag GHOST_SystemWayland::getCapabilities() const * screen-shot and eye-dropper sampling logic, both operations where the overhead * is negligible. */ GHOST_kCapabilityGPUReadFrontBuffer | + /* This WAYLAND back-end has not yet implemented desktop color sample. */ + GHOST_kCapabilityDesktopSample | /* This WAYLAND back-end has not yet implemented image copy/paste. */ GHOST_kCapabilityClipboardImages)); } diff --git a/intern/ghost/intern/GHOST_SystemWin32.cc b/intern/ghost/intern/GHOST_SystemWin32.cc index 993115cbffe..9d13e73c28a 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.cc +++ b/intern/ghost/intern/GHOST_SystemWin32.cc @@ -448,6 +448,31 @@ GHOST_TSuccess GHOST_SystemWin32::setCursorPosition(int32_t x, int32_t y) return ::SetCursorPos(x, y) == TRUE ? GHOST_kSuccess : GHOST_kFailure; } +GHOST_TSuccess GHOST_SystemWin32::getPixelAtCursor(float r_color[3]) const +{ + POINT point; + if (!GetCursorPos(&point)) { + return GHOST_kFailure; + } + + HDC dc = GetDC(NULL); + if (dc == NULL) { + return GHOST_kFailure; + } + + COLORREF color = GetPixel(dc, point.x, point.y); + ReleaseDC(NULL, dc); + + if (color == CLR_INVALID) { + return GHOST_kFailure; + } + + r_color[0] = GetRValue(color) / 255.0f; + r_color[1] = GetGValue(color) / 255.0f; + r_color[2] = GetBValue(color) / 255.0f; + return GHOST_kSuccess; +} + GHOST_TSuccess GHOST_SystemWin32::getModifierKeys(GHOST_ModifierKeys &keys) const { /* `GetAsyncKeyState` returns the current interrupt-level state of the hardware, which is needed diff --git a/intern/ghost/intern/GHOST_SystemWin32.hh b/intern/ghost/intern/GHOST_SystemWin32.hh index 5437e3667b7..8fa578f7cd3 100644 --- a/intern/ghost/intern/GHOST_SystemWin32.hh +++ b/intern/ghost/intern/GHOST_SystemWin32.hh @@ -182,6 +182,13 @@ class GHOST_SystemWin32 : public GHOST_System { */ GHOST_TSuccess setCursorPosition(int32_t x, int32_t y); + /** + * Get the color of the pixel at the current mouse cursor location + * \param r_color: returned sRGB float colors + * \return Success value (true == successful and supported by platform) + */ + GHOST_TSuccess getPixelAtCursor(float r_color[3]) const; + /*************************************************************************************** ** Access to mouse button and keyboard states. ***************************************************************************************/ diff --git a/intern/ghost/intern/GHOST_SystemX11.cc b/intern/ghost/intern/GHOST_SystemX11.cc index 90939dda816..78d07331e3c 100644 --- a/intern/ghost/intern/GHOST_SystemX11.cc +++ b/intern/ghost/intern/GHOST_SystemX11.cc @@ -1693,6 +1693,8 @@ GHOST_TCapabilityFlag GHOST_SystemX11::getCapabilities() const { return GHOST_TCapabilityFlag(GHOST_CAPABILITY_FLAG_ALL & ~( + /* No support yet for desktop sampling. */ + GHOST_kCapabilityDesktopSample | /* No support yet for image copy/paste. */ GHOST_kCapabilityClipboardImages)); } diff --git a/source/blender/editors/interface/eyedroppers/eyedropper_color.cc b/source/blender/editors/interface/eyedroppers/eyedropper_color.cc index a88251fa736..ddd66202f9b 100644 --- a/source/blender/editors/interface/eyedroppers/eyedropper_color.cc +++ b/source/blender/editors/interface/eyedroppers/eyedropper_color.cc @@ -315,45 +315,35 @@ static bool eyedropper_cryptomatte_sample_fl(bContext *C, void eyedropper_color_sample_fl(bContext *C, const int m_xy[2], float r_col[3]) { - /* we could use some clever */ - Main *bmain = CTX_data_main(C); - const char *display_device = CTX_data_scene(C)->display_settings.display_device; - ColorManagedDisplay *display = IMB_colormanagement_display_get_named(display_device); + wmWindowManager *wm = CTX_wm_manager(C); + ScrArea *area = nullptr; int mval[2]; - wmWindow *win; - ScrArea *area; - datadropper_win_area_find(C, m_xy, mval, &win, &area); + wmWindow *win = WM_window_find_under_cursor(CTX_wm_window(C), m_xy, mval); + if (win) { + bScreen *screen = WM_window_get_active_screen(win); + area = BKE_screen_find_area_xy(screen, SPACE_TYPE_ANY, mval); + } if (area) { - if (area->spacetype == SPACE_IMAGE) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); - if (region) { + ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); + if (region) { + const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; + if (area->spacetype == SPACE_IMAGE) { SpaceImage *sima = static_cast(area->spacedata.first); - const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; - if (ED_space_image_color_sample(sima, region, region_mval, r_col, nullptr)) { return; } } - } - else if (area->spacetype == SPACE_NODE) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); - if (region) { + else if (area->spacetype == SPACE_NODE) { SpaceNode *snode = static_cast(area->spacedata.first); - const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; - + Main *bmain = CTX_data_main(C); if (ED_space_node_color_sample(bmain, snode, region, region_mval, r_col)) { return; } } - } - else if (area->spacetype == SPACE_CLIP) { - ARegion *region = BKE_area_find_region_xy(area, RGN_TYPE_WINDOW, mval); - if (region) { + else if (area->spacetype == SPACE_CLIP) { SpaceClip *sc = static_cast(area->spacedata.first); - const int region_mval[2] = {mval[0] - region->winrct.xmin, mval[1] - region->winrct.ymin}; - if (ED_space_clip_color_sample(sc, region, region_mval, r_col)) { return; } @@ -362,9 +352,20 @@ void eyedropper_color_sample_fl(bContext *C, const int m_xy[2], float r_col[3]) } if (win) { - WM_window_pixels_read_sample(C, win, mval, r_col); + /* Other areas within a Blender window. */ + if (!WM_window_pixels_read_sample(C, win, mval, r_col)) { + WM_window_pixels_read_sample_from_offscreen(C, win, mval, r_col); + } + const char *display_device = CTX_data_scene(C)->display_settings.display_device; + ColorManagedDisplay *display = IMB_colormanagement_display_get_named(display_device); IMB_colormanagement_display_to_scene_linear_v3(r_col, display); } + else if ((WM_capabilities_flag() & WM_CAPABILITY_DESKTOP_SAMPLE) && + WM_desktop_cursor_sample_read(r_col)) + { + /* Outside of the Blender window if we support it. */ + IMB_colormanagement_srgb_to_scene_linear_v3(r_col, r_col); + } else { zero_v3(r_col); } diff --git a/source/blender/windowmanager/WM_api.hh b/source/blender/windowmanager/WM_api.hh index 3186d36cdb5..7f62a649ed0 100644 --- a/source/blender/windowmanager/WM_api.hh +++ b/source/blender/windowmanager/WM_api.hh @@ -170,6 +170,8 @@ enum eWM_CapabilitiesFlag { WM_CAPABILITY_GPU_FRONT_BUFFER_READ = (1 << 3), /** Ability to copy/paste system clipboard images. */ WM_CAPABILITY_CLIPBOARD_IMAGES = (1 << 4), + /** Ability to sample a color outside of Blender windows. */ + WM_CAPABILITY_DESKTOP_SAMPLE = (1 << 5), /** The initial value, indicates the value needs to be set by inspecting GHOST. */ WM_CAPABILITY_INITIALIZED = (1 << 31), }; @@ -194,6 +196,12 @@ wmWindow *WM_window_find_under_cursor(wmWindow *win, const int mval[2], int r_mv */ wmWindow *WM_window_find_by_area(wmWindowManager *wm, const ScrArea *area); +/** + * Return the color of the pixel at the current mouse cursor position on the desktop, whether in a + * Blender window or not. Returns false on failure or if not supported by the platform. + */ +bool WM_desktop_cursor_sample_read(float r_col[3]); + /** * Read pixels from the front-buffer (fast). * diff --git a/source/blender/windowmanager/intern/wm_draw.cc b/source/blender/windowmanager/intern/wm_draw.cc index 63c8925b84a..ea8cecdb77f 100644 --- a/source/blender/windowmanager/intern/wm_draw.cc +++ b/source/blender/windowmanager/intern/wm_draw.cc @@ -1411,6 +1411,11 @@ bool WM_window_pixels_read_sample(bContext *C, wmWindow *win, const int pos[2], return WM_window_pixels_read_sample_from_offscreen(C, win, pos, r_col); } +bool WM_desktop_cursor_sample_read(float r_col[3]) +{ + return GHOST_GetPixelAtCursor(r_col); +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/windowmanager/intern/wm_window.cc b/source/blender/windowmanager/intern/wm_window.cc index 990c5cc37ef..f32f15285bf 100644 --- a/source/blender/windowmanager/intern/wm_window.cc +++ b/source/blender/windowmanager/intern/wm_window.cc @@ -1887,6 +1887,9 @@ eWM_CapabilitiesFlag WM_capabilities_flag() if (ghost_flag & GHOST_kCapabilityClipboardImages) { flag |= WM_CAPABILITY_CLIPBOARD_IMAGES; } + if (ghost_flag & GHOST_kCapabilityDesktopSample) { + flag |= WM_CAPABILITY_DESKTOP_SAMPLE; + } return flag; }