ce53c5cc70
Re-fetch texture when cursor's either width or height changed. When the cursor icon changed and the new one has the same width or height, it may not recreate the texture and thus cause memory access violation.
247 lines
5.2 KiB
C
247 lines
5.2 KiB
C
#include <windows.h>
|
|
#include <obs.h>
|
|
#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 % 8) & 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 gs_texture_t *get_cached_texture(struct cursor_data *data,
|
|
uint32_t cx, uint32_t cy)
|
|
{
|
|
struct cached_cursor cc;
|
|
|
|
for (size_t i = 0; i < data->cached_textures.num; i++) {
|
|
struct cached_cursor *pcc = &data->cached_textures.array[i];
|
|
|
|
if (pcc->cx == cx && pcc->cy == cy)
|
|
return pcc->texture;
|
|
}
|
|
|
|
cc.texture = gs_texture_create(cx, cy, GS_BGRA, 1, NULL, GS_DYNAMIC);
|
|
cc.cx = cx;
|
|
cc.cy = cy;
|
|
da_push_back(data->cached_textures, &cc);
|
|
return cc.texture;
|
|
}
|
|
|
|
static inline bool cursor_capture_icon(struct cursor_data *data, HICON icon)
|
|
{
|
|
uint8_t *bitmap;
|
|
uint32_t height;
|
|
uint32_t width;
|
|
ICONINFO ii;
|
|
|
|
if (!icon) {
|
|
return false;
|
|
}
|
|
if (!GetIconInfo(icon, &ii)) {
|
|
return false;
|
|
}
|
|
|
|
bitmap = cursor_capture_icon_bitmap(&ii, &width, &height);
|
|
if (bitmap) {
|
|
if (data->last_cx != width || data->last_cy != height) {
|
|
data->texture = get_cached_texture(data, width, height);
|
|
data->last_cx = width;
|
|
data->last_cy = height;
|
|
}
|
|
gs_texture_set_image(data->texture, bitmap, width * 4, false);
|
|
bfree(bitmap);
|
|
|
|
data->x_hotspot = ii.xHotspot;
|
|
data->y_hotspot = ii.yHotspot;
|
|
}
|
|
|
|
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 width, long height)
|
|
{
|
|
long x = data->cursor_pos.x + x_offset;
|
|
long y = data->cursor_pos.y + y_offset;
|
|
long x_draw = x - data->x_hotspot;
|
|
long y_draw = y - data->y_hotspot;
|
|
|
|
if (x < 0 || x > width || y < 0 || y > height)
|
|
return;
|
|
|
|
if (data->visible && !!data->texture) {
|
|
gs_blend_state_push();
|
|
gs_blend_function(GS_BLEND_SRCALPHA, GS_BLEND_INVSRCALPHA);
|
|
gs_enable_color(true, true, true, false);
|
|
|
|
gs_matrix_push();
|
|
gs_matrix_scale3f(x_scale, y_scale, 1.0f);
|
|
obs_source_draw(data->texture, x_draw, y_draw, 0, 0, false);
|
|
gs_matrix_pop();
|
|
|
|
gs_enable_color(true, true, true, true);
|
|
gs_blend_state_pop();
|
|
}
|
|
}
|
|
|
|
void cursor_data_free(struct cursor_data *data)
|
|
{
|
|
for (size_t i = 0; i < data->cached_textures.num; i++) {
|
|
struct cached_cursor *pcc = &data->cached_textures.array[i];
|
|
gs_texture_destroy(pcc->texture);
|
|
}
|
|
da_free(data->cached_textures);
|
|
memset(data, 0, sizeof(*data));
|
|
}
|