diff --git a/plugins/win-capture/CMakeLists.txt b/plugins/win-capture/CMakeLists.txt index 17f2b9a60..1b7ddb94a 100644 --- a/plugins/win-capture/CMakeLists.txt +++ b/plugins/win-capture/CMakeLists.txt @@ -3,6 +3,7 @@ project(win-capture) set(win-capture_HEADERS obfuscate.h hook-helpers.h + cursor-capture.h graphics-hook-info.h window-helpers.h dc-capture.h) @@ -10,6 +11,7 @@ set(win-capture_HEADERS set(win-capture_SOURCES dc-capture.c obfuscate.c + cursor-capture.c window-helpers.c monitor-capture.c window-capture.c diff --git a/plugins/win-capture/cursor-capture.c b/plugins/win-capture/cursor-capture.c new file mode 100644 index 000000000..b244de98b --- /dev/null +++ b/plugins/win-capture/cursor-capture.c @@ -0,0 +1,208 @@ +#include +#include +#include "cursor-capture.h" + +static uint8_t *get_bitmap_data(HBITMAP hbmp, BITMAP *bmp) +{ + if (GetObject(hbmp, sizeof(*bmp), bmp) != 0) { + uint8_t *output; + unsigned int size = + (bmp->bmHeight * bmp->bmWidth * bmp->bmBitsPixel) / 8; + + output = bmalloc(size); + GetBitmapBits(hbmp, size, output); + return output; + } + + return NULL; +} + +static inline uint8_t bit_to_alpha(uint8_t *data, long pixel, bool invert) +{ + uint8_t pix_byte = data[pixel / 8]; + bool alpha = (pix_byte >> (7 - pixel % 7) & 1) != 0; + + if (invert) { + return alpha ? 0xFF : 0; + } else { + return alpha ? 0 : 0xFF; + } +} + +static inline bool bitmap_has_alpha(uint8_t *data, long num_pixels) +{ + for (long i = 0; i < num_pixels; i++) { + if (data[i * 4 + 3] != 0) { + return true; + } + } + + return false; +} + +static inline void apply_mask(uint8_t *color, uint8_t *mask, long num_pixels) +{ + for (long i = 0; i < num_pixels; i++) + color[i * 4 + 3] = bit_to_alpha(mask, i, false); +} + +static inline uint8_t *copy_from_color(ICONINFO *ii, uint32_t *width, + uint32_t *height) +{ + BITMAP bmp_color; + BITMAP bmp_mask; + uint8_t *color; + uint8_t *mask; + + color = get_bitmap_data(ii->hbmColor, &bmp_color); + if (!color) { + return NULL; + } + + if (bmp_color.bmBitsPixel < 32) { + bfree(color); + return NULL; + } + + mask = get_bitmap_data(ii->hbmMask, &bmp_mask); + if (mask) { + long pixels = bmp_color.bmHeight * bmp_color.bmWidth; + + if (!bitmap_has_alpha(color, pixels)) + apply_mask(color, mask, pixels); + + bfree(mask); + } + + *width = bmp_color.bmWidth; + *height = bmp_color.bmHeight; + return color; +} + +static inline uint8_t *copy_from_mask(ICONINFO *ii, uint32_t *width, + uint32_t *height) +{ + uint8_t *output; + uint8_t *mask; + long pixels; + long bottom; + BITMAP bmp; + + mask = get_bitmap_data(ii->hbmMask, &bmp); + if (!mask) { + return NULL; + } + + bmp.bmHeight /= 2; + + pixels = bmp.bmHeight * bmp.bmWidth; + output = bzalloc(pixels * 4); + + bottom = bmp.bmWidthBytes * bmp.bmHeight; + + for (long i = 0; i < pixels; i++) { + uint8_t alpha = bit_to_alpha(mask, i, false); + uint8_t color = bit_to_alpha(mask + bottom, i, true); + + if (!alpha) { + output[i * 4 + 3] = color; + } else { + *(uint32_t*)&output[i * 4] = !!color ? + 0xFFFFFFFF : 0xFF000000; + } + } + + bfree(mask); + + *width = bmp.bmWidth; + *height = bmp.bmHeight; + return output; +} + +static inline uint8_t *cursor_capture_icon_bitmap(ICONINFO *ii, + uint32_t *width, uint32_t *height) +{ + uint8_t *output; + + output = copy_from_color(ii, width, height); + if (!output) + output = copy_from_mask(ii, width, height); + + return output; +} + +static inline bool cursor_capture_icon(struct cursor_data *data, HICON icon) +{ + bool success = false; + uint8_t *bitmap; + uint32_t height; + uint32_t width; + ICONINFO ii; + + gs_texture_destroy(data->texture); + data->texture = NULL; + + if (!icon) { + return false; + } + if (!GetIconInfo(icon, &ii)) { + return false; + } + + bitmap = cursor_capture_icon_bitmap(&ii, &width, &height); + if (bitmap) { + data->texture = gs_texture_create(width, height, GS_BGRA, + 1, &bitmap, 0); + bfree(bitmap); + } + + DeleteObject(ii.hbmColor); + DeleteObject(ii.hbmMask); + return !!data->texture; +} + +void cursor_capture(struct cursor_data *data) +{ + CURSORINFO ci = {0}; + HICON icon; + + ci.cbSize = sizeof(ci); + + if (!GetCursorInfo(&ci)) { + data->visible = false; + return; + } + + memcpy(&data->cursor_pos, &ci.ptScreenPos, sizeof(data->cursor_pos)); + + if (data->current_cursor == ci.hCursor) { + return; + } + + icon = CopyIcon(ci.hCursor); + data->visible = cursor_capture_icon(data, icon); + data->current_cursor = ci.hCursor; + if ((ci.flags & CURSOR_SHOWING) == 0) + data->visible = false; + DestroyIcon(icon); +} + +void cursor_draw(struct cursor_data *data, long x_offset, long y_offset, + float x_scale, float y_scale) +{ + long x = data->cursor_pos.x + x_offset - data->x_hotspot; + long y = data->cursor_pos.y + y_offset - data->y_hotspot; + + if (data->visible && !!data->texture) { + gs_matrix_push(); + gs_matrix_scale3f(x_scale, y_scale, 1.0f); + obs_source_draw(data->texture, x, y, 0, 0, false); + gs_matrix_pop(); + } +} + +void cursor_data_free(struct cursor_data *data) +{ + gs_texture_destroy(data->texture); + memset(data, 0, sizeof(*data)); +} diff --git a/plugins/win-capture/cursor-capture.h b/plugins/win-capture/cursor-capture.h new file mode 100644 index 000000000..5db7708ca --- /dev/null +++ b/plugins/win-capture/cursor-capture.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +struct cursor_data { + gs_texture_t *texture; + HCURSOR current_cursor; + POINT cursor_pos; + long x_hotspot; + long y_hotspot; + bool visible; +}; + +extern void cursor_capture(struct cursor_data *data); +extern void cursor_draw(struct cursor_data *data, long x_offset, long y_offset, + float x_scale, float y_scale); +extern void cursor_data_free(struct cursor_data *data);