obs-studio/plugins/win-capture/cursor-capture.c
jp9000 e20ec366b2 Make capture sources w/o alpha use opaque effect
This fixes an issue primarily with filter rendering: when capturing
windows and displays, their alpha channel is almost always 0, causing
the image to be completely invisible unintentionally.  The original fix
for this for many sources was just to turn off the blending, which would
be fine if you're not rendering any filters, but filters will render to
render targets first, and that lack of alpha will end up carrying over
in to the final image.

This doesn't apply to any mac captures because mac actually seems to set
the alpha channel to 1.
2015-03-22 19:18:05 -07:00

223 lines
4.5 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 % 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)
{
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, (const uint8_t**)&bitmap, 0);
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)
{
gs_texture_destroy(data->texture);
memset(data, 0, sizeof(*data));
}