2019-02-03 13:47:36 -05:00
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
// Copyright (c) Yuxuan Shui <yshuiv7@gmail.com>
|
2019-08-09 19:56:04 -04:00
|
|
|
#include <math.h>
|
2020-02-07 16:27:01 -05:00
|
|
|
#include <string.h>
|
2019-01-20 11:53:39 -05:00
|
|
|
#include <xcb/render.h>
|
2019-02-20 13:17:16 -05:00
|
|
|
#include <xcb/xcb_image.h>
|
2019-01-20 16:15:20 -05:00
|
|
|
#include <xcb/xcb_renderutil.h>
|
2018-10-03 17:46:18 -04:00
|
|
|
|
2019-02-03 10:59:31 -05:00
|
|
|
#include "backend/backend.h"
|
|
|
|
#include "backend/backend_common.h"
|
2018-12-31 09:27:18 -05:00
|
|
|
#include "common.h"
|
2019-08-09 19:56:04 -04:00
|
|
|
#include "config.h"
|
2020-02-07 16:27:01 -05:00
|
|
|
#include "kernel.h"
|
2019-01-20 11:53:39 -05:00
|
|
|
#include "log.h"
|
2020-02-07 16:27:01 -05:00
|
|
|
#include "utils.h"
|
2019-02-03 13:47:36 -05:00
|
|
|
#include "win.h"
|
2019-02-20 13:17:16 -05:00
|
|
|
#include "x.h"
|
2018-10-03 17:46:18 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a 1x1 <code>Picture</code> of a particular color.
|
|
|
|
*/
|
2023-06-29 00:39:36 -04:00
|
|
|
xcb_render_picture_t
|
|
|
|
solid_picture(struct x_connection *c, bool argb, double a, double r, double g, double b) {
|
2018-10-03 17:46:18 -04:00
|
|
|
xcb_pixmap_t pixmap;
|
|
|
|
xcb_render_picture_t picture;
|
|
|
|
xcb_render_create_picture_value_list_t pa;
|
|
|
|
xcb_render_color_t col;
|
|
|
|
xcb_rectangle_t rect;
|
|
|
|
|
2023-06-29 00:39:36 -04:00
|
|
|
pixmap = x_create_pixmap(c, argb ? 32 : 8, 1, 1);
|
|
|
|
if (!pixmap) {
|
2018-12-31 09:27:18 -05:00
|
|
|
return XCB_NONE;
|
2023-06-29 00:39:36 -04:00
|
|
|
}
|
2018-10-03 17:46:18 -04:00
|
|
|
|
2018-12-31 09:27:18 -05:00
|
|
|
pa.repeat = 1;
|
2018-10-03 17:46:18 -04:00
|
|
|
picture = x_create_picture_with_standard_and_pixmap(
|
2019-02-26 18:21:11 -05:00
|
|
|
c, argb ? XCB_PICT_STANDARD_ARGB_32 : XCB_PICT_STANDARD_A_8, pixmap,
|
2018-10-03 17:46:18 -04:00
|
|
|
XCB_RENDER_CP_REPEAT, &pa);
|
|
|
|
|
|
|
|
if (!picture) {
|
2023-06-29 00:39:36 -04:00
|
|
|
xcb_free_pixmap(c->c, pixmap);
|
2018-12-31 09:27:18 -05:00
|
|
|
return XCB_NONE;
|
2018-10-03 17:46:18 -04:00
|
|
|
}
|
|
|
|
|
2019-03-30 05:07:21 -04:00
|
|
|
col.alpha = (uint16_t)(a * 0xffff);
|
|
|
|
col.red = (uint16_t)(r * 0xffff);
|
|
|
|
col.green = (uint16_t)(g * 0xffff);
|
|
|
|
col.blue = (uint16_t)(b * 0xffff);
|
2018-10-03 17:46:18 -04:00
|
|
|
|
|
|
|
rect.x = 0;
|
|
|
|
rect.y = 0;
|
|
|
|
rect.width = 1;
|
|
|
|
rect.height = 1;
|
|
|
|
|
2023-06-29 00:39:36 -04:00
|
|
|
xcb_render_fill_rectangles(c->c, XCB_RENDER_PICT_OP_SRC, picture, col, 1, &rect);
|
|
|
|
xcb_free_pixmap(c->c, pixmap);
|
2018-10-03 17:46:18 -04:00
|
|
|
|
|
|
|
return picture;
|
|
|
|
}
|
|
|
|
|
2023-06-29 00:39:36 -04:00
|
|
|
xcb_image_t *make_shadow(struct x_connection *c, const conv *kernel, double opacity,
|
|
|
|
int width, int height) {
|
2018-12-31 19:15:51 -05:00
|
|
|
/*
|
|
|
|
* We classify shadows into 4 kinds of regions
|
|
|
|
* r = shadow radius
|
|
|
|
* (0, 0) is the top left of the window itself
|
|
|
|
* -r r width-r width+r
|
|
|
|
* -r +-----+---------+-----+
|
|
|
|
* | 1 | 2 | 1 |
|
|
|
|
* r +-----+---------+-----+
|
|
|
|
* | 2 | 3 | 2 |
|
|
|
|
* height-r +-----+---------+-----+
|
|
|
|
* | 1 | 2 | 1 |
|
|
|
|
* height+r +-----+---------+-----+
|
|
|
|
*/
|
2018-12-31 09:27:18 -05:00
|
|
|
xcb_image_t *ximage;
|
2019-01-01 06:35:59 -05:00
|
|
|
const double *shadow_sum = kernel->rsum;
|
2019-02-20 13:17:16 -05:00
|
|
|
assert(shadow_sum);
|
2019-02-17 16:54:35 -05:00
|
|
|
// We only support square kernels for shadow
|
|
|
|
assert(kernel->w == kernel->h);
|
2019-03-30 05:07:21 -04:00
|
|
|
int d = kernel->w;
|
|
|
|
int r = d / 2;
|
2018-12-31 19:15:51 -05:00
|
|
|
int swidth = width + r * 2, sheight = height + r * 2;
|
2018-12-31 09:27:18 -05:00
|
|
|
|
2018-12-31 19:15:51 -05:00
|
|
|
assert(d % 2 == 1);
|
|
|
|
assert(d > 0);
|
|
|
|
|
2023-06-29 00:39:36 -04:00
|
|
|
ximage =
|
|
|
|
xcb_image_create_native(c->c, to_u16_checked(swidth), to_u16_checked(sheight),
|
|
|
|
XCB_IMAGE_FORMAT_Z_PIXMAP, 8, 0, 0, NULL);
|
2018-12-31 09:27:18 -05:00
|
|
|
if (!ximage) {
|
|
|
|
log_error("failed to create an X image");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned char *data = ximage->data;
|
2021-11-25 03:21:41 -05:00
|
|
|
long long sstride = ximage->stride;
|
2018-12-31 09:27:18 -05:00
|
|
|
|
2018-12-31 19:15:51 -05:00
|
|
|
// If the window body is smaller than the kernel, we do convolution directly
|
|
|
|
if (width < r * 2 && height < r * 2) {
|
|
|
|
for (int y = 0; y < sheight; y++) {
|
|
|
|
for (int x = 0; x < swidth; x++) {
|
|
|
|
double sum = sum_kernel_normalized(
|
|
|
|
kernel, d - x - 1, d - y - 1, width, height);
|
2021-09-21 12:01:18 -04:00
|
|
|
data[y * sstride + x] = (uint8_t)(sum * 255.0 * opacity);
|
2018-12-31 19:15:51 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return ximage;
|
2018-12-31 09:27:18 -05:00
|
|
|
}
|
|
|
|
|
2018-12-31 19:15:51 -05:00
|
|
|
if (height < r * 2) {
|
2019-03-30 05:07:21 -04:00
|
|
|
// Implies width >= r * 2
|
2018-12-31 19:15:51 -05:00
|
|
|
// If the window height is smaller than the kernel, we divide
|
|
|
|
// the window like this:
|
|
|
|
// -r r width-r width+r
|
|
|
|
// +------+-------------+------+
|
|
|
|
// | | | |
|
|
|
|
// +------+-------------+------+
|
|
|
|
for (int y = 0; y < sheight; y++) {
|
|
|
|
for (int x = 0; x < r * 2; x++) {
|
|
|
|
double sum = sum_kernel_normalized(kernel, d - x - 1,
|
|
|
|
d - y - 1, d, height) *
|
2021-09-21 12:01:18 -04:00
|
|
|
255.0 * opacity;
|
2019-03-30 05:07:21 -04:00
|
|
|
data[y * sstride + x] = (uint8_t)sum;
|
|
|
|
data[y * sstride + swidth - x - 1] = (uint8_t)sum;
|
2018-12-31 09:27:18 -05:00
|
|
|
}
|
|
|
|
}
|
2018-12-31 19:15:51 -05:00
|
|
|
for (int y = 0; y < sheight; y++) {
|
2021-09-21 12:01:18 -04:00
|
|
|
double sum = sum_kernel_normalized(kernel, 0, d - y - 1, d, height) *
|
|
|
|
255.0 * opacity;
|
2019-03-30 05:07:21 -04:00
|
|
|
memset(&data[y * sstride + r * 2], (uint8_t)sum,
|
|
|
|
(size_t)(width - 2 * r));
|
2018-12-31 19:15:51 -05:00
|
|
|
}
|
|
|
|
return ximage;
|
2018-12-31 09:27:18 -05:00
|
|
|
}
|
2018-12-31 19:15:51 -05:00
|
|
|
if (width < r * 2) {
|
|
|
|
// Similarly, for width smaller than kernel
|
|
|
|
for (int y = 0; y < r * 2; y++) {
|
|
|
|
for (int x = 0; x < swidth; x++) {
|
|
|
|
double sum = sum_kernel_normalized(kernel, d - x - 1,
|
|
|
|
d - y - 1, width, d) *
|
2021-09-21 12:01:18 -04:00
|
|
|
255.0 * opacity;
|
2019-03-30 05:07:21 -04:00
|
|
|
data[y * sstride + x] = (uint8_t)sum;
|
|
|
|
data[(sheight - y - 1) * sstride + x] = (uint8_t)sum;
|
2018-12-31 19:15:51 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for (int x = 0; x < swidth; x++) {
|
2021-09-21 12:01:18 -04:00
|
|
|
double sum = sum_kernel_normalized(kernel, d - x - 1, 0, width, d) *
|
|
|
|
255.0 * opacity;
|
2018-12-31 19:15:51 -05:00
|
|
|
for (int y = r * 2; y < height; y++) {
|
2019-03-30 05:07:21 -04:00
|
|
|
data[y * sstride + x] = (uint8_t)sum;
|
2018-12-31 09:27:18 -05:00
|
|
|
}
|
|
|
|
}
|
2018-12-31 19:15:51 -05:00
|
|
|
return ximage;
|
2018-12-31 09:27:18 -05:00
|
|
|
}
|
|
|
|
|
2019-03-30 05:07:21 -04:00
|
|
|
// Implies: width >= r * 2 && height >= r * 2
|
|
|
|
|
2018-12-31 19:15:51 -05:00
|
|
|
// Fill part 3
|
|
|
|
for (int y = r; y < height + r; y++) {
|
2019-03-30 05:07:21 -04:00
|
|
|
memset(data + sstride * y + r, (uint8_t)(255 * opacity), (size_t)width);
|
2018-12-31 19:15:51 -05:00
|
|
|
}
|
2018-12-31 09:27:18 -05:00
|
|
|
|
2018-12-31 19:15:51 -05:00
|
|
|
// Part 1
|
|
|
|
for (int y = 0; y < r * 2; y++) {
|
|
|
|
for (int x = 0; x < r * 2; x++) {
|
|
|
|
double tmpsum = shadow_sum[y * d + x] * opacity * 255.0;
|
2019-03-30 05:07:21 -04:00
|
|
|
data[y * sstride + x] = (uint8_t)tmpsum;
|
|
|
|
data[(sheight - y - 1) * sstride + x] = (uint8_t)tmpsum;
|
|
|
|
data[(sheight - y - 1) * sstride + (swidth - x - 1)] = (uint8_t)tmpsum;
|
|
|
|
data[y * sstride + (swidth - x - 1)] = (uint8_t)tmpsum;
|
2018-12-31 09:27:18 -05:00
|
|
|
}
|
2018-12-31 19:15:51 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Part 2, top/bottom
|
|
|
|
for (int y = 0; y < r * 2; y++) {
|
|
|
|
double tmpsum = shadow_sum[d * y + d - 1] * opacity * 255.0;
|
2019-03-30 05:07:21 -04:00
|
|
|
memset(&data[y * sstride + r * 2], (uint8_t)tmpsum, (size_t)(width - r * 2));
|
|
|
|
memset(&data[(sheight - y - 1) * sstride + r * 2], (uint8_t)tmpsum,
|
|
|
|
(size_t)(width - r * 2));
|
2018-12-31 19:15:51 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Part 2, left/right
|
|
|
|
for (int x = 0; x < r * 2; x++) {
|
|
|
|
double tmpsum = shadow_sum[d * (d - 1) + x] * opacity * 255.0;
|
|
|
|
for (int y = r * 2; y < height; y++) {
|
2019-03-30 05:07:21 -04:00
|
|
|
data[y * sstride + x] = (uint8_t)tmpsum;
|
|
|
|
data[y * sstride + (swidth - x - 1)] = (uint8_t)tmpsum;
|
2018-12-31 09:27:18 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ximage;
|
|
|
|
}
|
|
|
|
|
2018-10-03 17:46:18 -04:00
|
|
|
/**
|
|
|
|
* Generate shadow <code>Picture</code> for a window.
|
|
|
|
*/
|
2023-06-29 00:39:36 -04:00
|
|
|
bool build_shadow(struct x_connection *c, double opacity, const int width,
|
2019-02-26 18:21:11 -05:00
|
|
|
const int height, const conv *kernel, xcb_render_picture_t shadow_pixel,
|
2019-02-20 13:17:16 -05:00
|
|
|
xcb_pixmap_t *pixmap, xcb_render_picture_t *pict) {
|
2018-10-03 17:46:18 -04:00
|
|
|
xcb_image_t *shadow_image = NULL;
|
2019-01-20 11:53:39 -05:00
|
|
|
xcb_pixmap_t shadow_pixmap = XCB_NONE, shadow_pixmap_argb = XCB_NONE;
|
|
|
|
xcb_render_picture_t shadow_picture = XCB_NONE, shadow_picture_argb = XCB_NONE;
|
|
|
|
xcb_gcontext_t gc = XCB_NONE;
|
2018-10-03 17:46:18 -04:00
|
|
|
|
2019-02-26 18:21:11 -05:00
|
|
|
shadow_image = make_shadow(c, kernel, opacity, width, height);
|
2018-10-03 17:46:18 -04:00
|
|
|
if (!shadow_image) {
|
|
|
|
log_error("Failed to make shadow");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-06-29 00:39:36 -04:00
|
|
|
shadow_pixmap = x_create_pixmap(c, 8, shadow_image->width, shadow_image->height);
|
2018-10-03 17:46:18 -04:00
|
|
|
shadow_pixmap_argb =
|
2023-06-29 00:39:36 -04:00
|
|
|
x_create_pixmap(c, 32, shadow_image->width, shadow_image->height);
|
2018-10-03 17:46:18 -04:00
|
|
|
|
|
|
|
if (!shadow_pixmap || !shadow_pixmap_argb) {
|
|
|
|
log_error("Failed to create shadow pixmaps");
|
|
|
|
goto shadow_picture_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
shadow_picture = x_create_picture_with_standard_and_pixmap(
|
2019-02-26 18:21:11 -05:00
|
|
|
c, XCB_PICT_STANDARD_A_8, shadow_pixmap, 0, NULL);
|
2018-10-03 17:46:18 -04:00
|
|
|
shadow_picture_argb = x_create_picture_with_standard_and_pixmap(
|
2019-02-26 18:21:11 -05:00
|
|
|
c, XCB_PICT_STANDARD_ARGB_32, shadow_pixmap_argb, 0, NULL);
|
|
|
|
if (!shadow_picture || !shadow_picture_argb) {
|
2018-10-03 17:46:18 -04:00
|
|
|
goto shadow_picture_err;
|
2019-02-26 18:21:11 -05:00
|
|
|
}
|
2018-10-03 17:46:18 -04:00
|
|
|
|
2019-04-08 00:49:39 -04:00
|
|
|
gc = x_new_id(c);
|
2023-06-29 00:39:36 -04:00
|
|
|
xcb_create_gc(c->c, gc, shadow_pixmap, 0, NULL);
|
2018-10-03 17:46:18 -04:00
|
|
|
|
2020-02-07 16:27:01 -05:00
|
|
|
// We need to make room for protocol metadata in the request. The metadata should
|
|
|
|
// be 24 bytes plus padding, let's be generous and give it 1kb
|
2023-06-29 00:39:36 -04:00
|
|
|
auto maximum_image_size = xcb_get_maximum_request_length(c->c) * 4 - 1024;
|
2020-02-07 16:27:01 -05:00
|
|
|
auto maximum_row =
|
|
|
|
to_u16_checked(clamp(maximum_image_size / shadow_image->stride, 0, UINT16_MAX));
|
|
|
|
if (maximum_row <= 0) {
|
2020-08-30 10:25:58 -04:00
|
|
|
// TODO(yshui) Upload image with XShm
|
2020-02-07 16:27:01 -05:00
|
|
|
log_error("X server request size limit is too restrictive, or the shadow "
|
|
|
|
"image is too wide for us to send a single row of the shadow "
|
|
|
|
"image. Shadow size: %dx%d",
|
|
|
|
width, height);
|
|
|
|
goto shadow_picture_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (uint32_t row = 0; row < shadow_image->height; row += maximum_row) {
|
|
|
|
auto batch_height = maximum_row;
|
|
|
|
if (batch_height > shadow_image->height - row) {
|
|
|
|
batch_height = to_u16_checked(shadow_image->height - row);
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t offset = row * shadow_image->stride / sizeof(*shadow_image->data);
|
2023-06-29 00:39:36 -04:00
|
|
|
xcb_put_image(c->c, (uint8_t)shadow_image->format, shadow_pixmap, gc,
|
2020-02-07 16:27:01 -05:00
|
|
|
shadow_image->width, batch_height, 0, to_i16_checked(row),
|
|
|
|
0, shadow_image->depth, shadow_image->stride * batch_height,
|
|
|
|
shadow_image->data + offset);
|
|
|
|
}
|
|
|
|
|
2023-06-29 00:39:36 -04:00
|
|
|
xcb_render_composite(c->c, XCB_RENDER_PICT_OP_SRC, shadow_pixel, shadow_picture,
|
2018-10-03 17:46:18 -04:00
|
|
|
shadow_picture_argb, 0, 0, 0, 0, 0, 0, shadow_image->width,
|
|
|
|
shadow_image->height);
|
|
|
|
|
|
|
|
*pixmap = shadow_pixmap_argb;
|
|
|
|
*pict = shadow_picture_argb;
|
|
|
|
|
2023-06-29 00:39:36 -04:00
|
|
|
xcb_free_gc(c->c, gc);
|
2018-10-03 17:46:18 -04:00
|
|
|
xcb_image_destroy(shadow_image);
|
2023-06-29 00:39:36 -04:00
|
|
|
xcb_free_pixmap(c->c, shadow_pixmap);
|
|
|
|
x_free_picture(c, shadow_picture);
|
2018-10-03 17:46:18 -04:00
|
|
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
shadow_picture_err:
|
2019-02-26 18:21:11 -05:00
|
|
|
if (shadow_image) {
|
2018-10-03 17:46:18 -04:00
|
|
|
xcb_image_destroy(shadow_image);
|
2019-02-26 18:21:11 -05:00
|
|
|
}
|
|
|
|
if (shadow_pixmap) {
|
2023-06-29 00:39:36 -04:00
|
|
|
xcb_free_pixmap(c->c, shadow_pixmap);
|
2019-02-26 18:21:11 -05:00
|
|
|
}
|
|
|
|
if (shadow_pixmap_argb) {
|
2023-06-29 00:39:36 -04:00
|
|
|
xcb_free_pixmap(c->c, shadow_pixmap_argb);
|
2019-02-26 18:21:11 -05:00
|
|
|
}
|
|
|
|
if (shadow_picture) {
|
2023-06-29 00:39:36 -04:00
|
|
|
x_free_picture(c, shadow_picture);
|
2019-02-26 18:21:11 -05:00
|
|
|
}
|
|
|
|
if (shadow_picture_argb) {
|
2023-06-29 00:39:36 -04:00
|
|
|
x_free_picture(c, shadow_picture_argb);
|
2019-02-26 18:21:11 -05:00
|
|
|
}
|
|
|
|
if (gc) {
|
2023-06-29 00:39:36 -04:00
|
|
|
xcb_free_gc(c->c, gc);
|
2019-02-26 18:21:11 -05:00
|
|
|
}
|
2018-10-03 17:46:18 -04:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2019-02-03 13:41:02 -05:00
|
|
|
|
2024-02-10 03:20:10 -05:00
|
|
|
image_handle default_render_shadow(backend_t *backend_data, int width, int height,
|
|
|
|
struct backend_shadow_context *sctx, struct color color) {
|
2022-08-25 00:21:19 -04:00
|
|
|
const conv *kernel = (void *)sctx;
|
2023-06-29 00:39:36 -04:00
|
|
|
xcb_render_picture_t shadow_pixel =
|
|
|
|
solid_picture(backend_data->c, true, 1, color.red, color.green, color.blue);
|
2023-02-12 18:23:53 -05:00
|
|
|
xcb_pixmap_t shadow = XCB_NONE;
|
2019-02-26 18:52:37 -05:00
|
|
|
xcb_render_picture_t pict = XCB_NONE;
|
|
|
|
|
2023-06-29 00:39:36 -04:00
|
|
|
if (!build_shadow(backend_data->c, color.alpha, width, height, kernel,
|
|
|
|
shadow_pixel, &shadow, &pict)) {
|
|
|
|
x_free_picture(backend_data->c, shadow_pixel);
|
2020-02-07 16:35:34 -05:00
|
|
|
return NULL;
|
|
|
|
}
|
2019-02-26 18:52:37 -05:00
|
|
|
|
|
|
|
auto visual = x_get_visual_for_standard(backend_data->c, XCB_PICT_STANDARD_ARGB_32);
|
2024-02-10 03:20:10 -05:00
|
|
|
auto ret = backend_data->ops->bind_pixmap(
|
2019-03-05 15:27:04 -05:00
|
|
|
backend_data, shadow, x_get_visual_info(backend_data->c, visual), true);
|
2023-06-29 00:39:36 -04:00
|
|
|
x_free_picture(backend_data->c, pict);
|
|
|
|
x_free_picture(backend_data->c, shadow_pixel);
|
2019-02-26 18:52:37 -05:00
|
|
|
return ret;
|
|
|
|
}
|
2019-04-27 10:07:23 -04:00
|
|
|
|
2022-08-25 12:53:31 -04:00
|
|
|
/// Implement render_shadow with shadow_from_mask
|
2024-02-10 03:20:10 -05:00
|
|
|
image_handle
|
2022-08-25 12:53:31 -04:00
|
|
|
backend_render_shadow_from_mask(backend_t *backend_data, int width, int height,
|
|
|
|
struct backend_shadow_context *sctx, struct color color) {
|
|
|
|
region_t reg;
|
|
|
|
pixman_region32_init_rect(®, 0, 0, (unsigned int)width, (unsigned int)height);
|
2024-02-10 03:20:10 -05:00
|
|
|
auto mask = backend_data->ops->make_mask(
|
2022-08-25 12:53:31 -04:00
|
|
|
backend_data, (geometry_t){.width = width, .height = height}, ®);
|
|
|
|
pixman_region32_fini(®);
|
|
|
|
|
2024-02-10 03:20:10 -05:00
|
|
|
auto shadow = backend_data->ops->shadow_from_mask(backend_data, mask, sctx, color);
|
2022-08-25 12:53:31 -04:00
|
|
|
backend_data->ops->release_image(backend_data, mask);
|
|
|
|
return shadow;
|
|
|
|
}
|
|
|
|
|
2022-08-25 00:21:19 -04:00
|
|
|
struct backend_shadow_context *
|
|
|
|
default_create_shadow_context(backend_t *backend_data attr_unused, double radius) {
|
|
|
|
auto ret =
|
|
|
|
(struct backend_shadow_context *)gaussian_kernel_autodetect_deviation(radius);
|
|
|
|
sum_kernel_preprocess((conv *)ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void default_destroy_shadow_context(backend_t *backend_data attr_unused,
|
|
|
|
struct backend_shadow_context *sctx) {
|
|
|
|
free_conv((conv *)sctx);
|
|
|
|
}
|
|
|
|
|
2020-02-07 16:27:01 -05:00
|
|
|
static struct conv **generate_box_blur_kernel(struct box_blur_args *args, int *kernel_count) {
|
2019-06-09 07:44:32 -04:00
|
|
|
int r = args->size * 2 + 1;
|
2019-06-06 01:53:41 -04:00
|
|
|
assert(r > 0);
|
2019-06-08 19:44:46 -04:00
|
|
|
auto ret = ccalloc(2, struct conv *);
|
2019-06-06 01:53:41 -04:00
|
|
|
ret[0] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r);
|
|
|
|
ret[1] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r);
|
2019-06-08 18:56:47 -04:00
|
|
|
ret[0]->w = r;
|
|
|
|
ret[0]->h = 1;
|
|
|
|
ret[1]->w = 1;
|
|
|
|
ret[1]->h = r;
|
2019-06-06 01:53:41 -04:00
|
|
|
for (int i = 0; i < r; i++) {
|
|
|
|
ret[0]->data[i] = 1;
|
|
|
|
ret[1]->data[i] = 1;
|
|
|
|
}
|
2019-06-08 19:44:46 -04:00
|
|
|
*kernel_count = 2;
|
2019-06-06 01:53:41 -04:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-06-08 19:44:46 -04:00
|
|
|
static struct conv **
|
2019-06-09 07:57:23 -04:00
|
|
|
generate_gaussian_blur_kernel(struct gaussian_blur_args *args, int *kernel_count) {
|
2019-06-09 07:44:32 -04:00
|
|
|
int r = args->size * 2 + 1;
|
2019-06-06 01:53:41 -04:00
|
|
|
assert(r > 0);
|
2019-06-08 19:44:46 -04:00
|
|
|
auto ret = ccalloc(2, struct conv *);
|
2019-06-06 01:53:41 -04:00
|
|
|
ret[0] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r);
|
|
|
|
ret[1] = cvalloc(sizeof(struct conv) + sizeof(double) * (size_t)r);
|
2019-06-08 18:56:47 -04:00
|
|
|
ret[0]->w = r;
|
|
|
|
ret[0]->h = 1;
|
|
|
|
ret[1]->w = 1;
|
|
|
|
ret[1]->h = r;
|
2019-06-09 07:44:32 -04:00
|
|
|
for (int i = 0; i <= args->size; i++) {
|
2019-06-08 18:56:47 -04:00
|
|
|
ret[0]->data[i] = ret[0]->data[r - i - 1] =
|
2019-06-09 07:44:32 -04:00
|
|
|
1.0 / (sqrt(2.0 * M_PI) * args->deviation) *
|
|
|
|
exp(-(args->size - i) * (args->size - i) /
|
|
|
|
(2 * args->deviation * args->deviation));
|
2019-06-08 19:44:46 -04:00
|
|
|
ret[1]->data[i] = ret[1]->data[r - i - 1] = ret[0]->data[i];
|
2019-06-06 01:53:41 -04:00
|
|
|
}
|
2019-06-08 19:44:46 -04:00
|
|
|
*kernel_count = 2;
|
2019-06-06 01:53:41 -04:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Generate blur kernels for gaussian and box blur methods. Generated kernel is not
|
|
|
|
/// normalized, and the center element will always be 1.
|
2019-06-09 07:44:32 -04:00
|
|
|
struct conv **generate_blur_kernel(enum blur_method method, void *args, int *kernel_count) {
|
2019-06-06 01:53:41 -04:00
|
|
|
switch (method) {
|
2019-06-09 07:44:32 -04:00
|
|
|
case BLUR_METHOD_BOX: return generate_box_blur_kernel(args, kernel_count);
|
2019-06-06 01:53:41 -04:00
|
|
|
case BLUR_METHOD_GAUSSIAN:
|
2019-06-09 07:44:32 -04:00
|
|
|
return generate_gaussian_blur_kernel(args, kernel_count);
|
2019-06-08 18:56:47 -04:00
|
|
|
default: break;
|
2019-06-06 01:53:41 -04:00
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2019-12-20 15:38:21 -05:00
|
|
|
/// Generate kernel parameters for dual-kawase blur method. Falls back on approximating
|
|
|
|
/// standard gauss radius if strength is zero or below.
|
|
|
|
struct dual_kawase_params *generate_dual_kawase_params(void *args) {
|
|
|
|
struct dual_kawase_blur_args *blur_args = args;
|
|
|
|
static const struct {
|
|
|
|
int iterations; /// Number of down- and upsample iterations
|
|
|
|
float offset; /// Sample offset in half-pixels
|
|
|
|
int min_radius; /// Approximate gauss-blur with at least this
|
|
|
|
/// radius and std-deviation
|
|
|
|
} strength_levels[20] = {
|
2023-07-21 17:22:02 -04:00
|
|
|
{.iterations = 1, .offset = 1.25F, .min_radius = 1}, // LVL 1
|
|
|
|
{.iterations = 1, .offset = 2.25F, .min_radius = 6}, // LVL 2
|
|
|
|
{.iterations = 2, .offset = 2.00F, .min_radius = 11}, // LVL 3
|
|
|
|
{.iterations = 2, .offset = 3.00F, .min_radius = 17}, // LVL 4
|
|
|
|
{.iterations = 2, .offset = 4.25F, .min_radius = 24}, // LVL 5
|
|
|
|
{.iterations = 3, .offset = 2.50F, .min_radius = 32}, // LVL 6
|
|
|
|
{.iterations = 3, .offset = 3.25F, .min_radius = 40}, // LVL 7
|
|
|
|
{.iterations = 3, .offset = 4.25F, .min_radius = 51}, // LVL 8
|
|
|
|
{.iterations = 3, .offset = 5.50F, .min_radius = 67}, // LVL 9
|
|
|
|
{.iterations = 4, .offset = 3.25F, .min_radius = 83}, // LVL 10
|
|
|
|
{.iterations = 4, .offset = 4.00F, .min_radius = 101}, // LVL 11
|
|
|
|
{.iterations = 4, .offset = 5.00F, .min_radius = 123}, // LVL 12
|
|
|
|
{.iterations = 4, .offset = 6.00F, .min_radius = 148}, // LVL 13
|
|
|
|
{.iterations = 4, .offset = 7.25F, .min_radius = 178}, // LVL 14
|
|
|
|
{.iterations = 4, .offset = 8.25F, .min_radius = 208}, // LVL 15
|
|
|
|
{.iterations = 5, .offset = 4.50F, .min_radius = 236}, // LVL 16
|
|
|
|
{.iterations = 5, .offset = 5.25F, .min_radius = 269}, // LVL 17
|
|
|
|
{.iterations = 5, .offset = 6.25F, .min_radius = 309}, // LVL 18
|
|
|
|
{.iterations = 5, .offset = 7.25F, .min_radius = 357}, // LVL 19
|
|
|
|
{.iterations = 5, .offset = 8.50F, .min_radius = 417}, // LVL 20
|
2019-12-20 15:38:21 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
auto params = ccalloc(1, struct dual_kawase_params);
|
|
|
|
params->iterations = 0;
|
2023-07-21 17:22:02 -04:00
|
|
|
params->offset = 1.0F;
|
2019-12-20 15:38:21 -05:00
|
|
|
|
|
|
|
if (blur_args->strength <= 0 && blur_args->size) {
|
|
|
|
// find highest level that approximates blur-strength with the selected
|
|
|
|
// gaussian blur-radius
|
|
|
|
int lvl = 1;
|
|
|
|
while (strength_levels[lvl - 1].min_radius < blur_args->size && lvl < 20) {
|
|
|
|
++lvl;
|
|
|
|
}
|
|
|
|
blur_args->strength = lvl;
|
|
|
|
}
|
|
|
|
if (blur_args->strength <= 0) {
|
|
|
|
// default value
|
|
|
|
blur_args->strength = 5;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(blur_args->strength > 0 && blur_args->strength <= 20);
|
|
|
|
params->iterations = strength_levels[blur_args->strength - 1].iterations;
|
|
|
|
params->offset = strength_levels[blur_args->strength - 1].offset;
|
|
|
|
|
|
|
|
// Expand sample area to cover the smallest texture / highest selected iteration:
|
|
|
|
// - Smallest texture dimensions are halved `iterations`-times
|
|
|
|
// - Upsample needs pixels two-times `offset` away from the border
|
|
|
|
// - Plus one for interpolation differences
|
2023-07-21 17:22:02 -04:00
|
|
|
params->expand = (1 << params->iterations) * 2 * (int)ceilf(params->offset) + 1;
|
2019-12-20 15:38:21 -05:00
|
|
|
|
|
|
|
return params;
|
|
|
|
}
|
|
|
|
|
2024-02-10 03:20:10 -05:00
|
|
|
image_handle default_clone_image(backend_t *base attr_unused, image_handle image,
|
|
|
|
const region_t *reg_visible attr_unused) {
|
2021-06-13 22:22:13 -04:00
|
|
|
auto new_img = ccalloc(1, struct backend_image);
|
2024-02-10 03:20:10 -05:00
|
|
|
*new_img = *(struct backend_image *)image;
|
2021-06-13 22:22:13 -04:00
|
|
|
new_img->inner->refcount++;
|
2024-02-10 03:20:10 -05:00
|
|
|
return (image_handle)new_img;
|
2021-06-13 22:22:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
bool default_set_image_property(backend_t *base attr_unused, enum image_properties op,
|
2024-02-10 03:20:10 -05:00
|
|
|
image_handle image, void *arg) {
|
|
|
|
auto tex = (struct backend_image *)image;
|
2021-06-13 22:22:13 -04:00
|
|
|
int *iargs = arg;
|
|
|
|
bool *bargs = arg;
|
|
|
|
double *dargs = arg;
|
|
|
|
switch (op) {
|
|
|
|
case IMAGE_PROPERTY_INVERTED: tex->color_inverted = bargs[0]; break;
|
|
|
|
case IMAGE_PROPERTY_DIM_LEVEL: tex->dim = dargs[0]; break;
|
|
|
|
case IMAGE_PROPERTY_OPACITY: tex->opacity = dargs[0]; break;
|
|
|
|
case IMAGE_PROPERTY_EFFECTIVE_SIZE:
|
|
|
|
// texture is already set to repeat, so nothing else we need to do
|
|
|
|
tex->ewidth = iargs[0];
|
|
|
|
tex->eheight = iargs[1];
|
|
|
|
break;
|
2021-07-16 14:11:23 -04:00
|
|
|
case IMAGE_PROPERTY_CORNER_RADIUS: tex->corner_radius = dargs[0]; break;
|
2021-06-13 22:22:13 -04:00
|
|
|
case IMAGE_PROPERTY_MAX_BRIGHTNESS: tex->max_brightness = dargs[0]; break;
|
2022-01-13 06:31:31 -05:00
|
|
|
case IMAGE_PROPERTY_BORDER_WIDTH: tex->border_width = *(int *)arg; break;
|
2022-07-17 09:24:18 -04:00
|
|
|
case IMAGE_PROPERTY_CUSTOM_SHADER: break;
|
2021-06-13 22:22:13 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-02-10 03:20:10 -05:00
|
|
|
bool default_is_image_transparent(backend_t *base attr_unused, image_handle image) {
|
|
|
|
auto img = (struct backend_image *)image;
|
2021-06-13 22:22:13 -04:00
|
|
|
return img->opacity < 1 || img->inner->has_alpha;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct backend_image *default_new_backend_image(int w, int h) {
|
|
|
|
auto ret = ccalloc(1, struct backend_image);
|
|
|
|
ret->opacity = 1;
|
|
|
|
ret->dim = 0;
|
|
|
|
ret->max_brightness = 1;
|
|
|
|
ret->eheight = h;
|
|
|
|
ret->ewidth = w;
|
|
|
|
ret->color_inverted = false;
|
2021-11-07 19:20:52 -05:00
|
|
|
ret->corner_radius = 0;
|
2021-06-13 22:22:13 -04:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-04-27 10:07:23 -04:00
|
|
|
void init_backend_base(struct backend_base *base, session_t *ps) {
|
2023-06-29 00:39:36 -04:00
|
|
|
base->c = &ps->c;
|
2019-04-27 10:07:23 -04:00
|
|
|
base->loop = ps->loop;
|
|
|
|
base->busy = false;
|
|
|
|
base->ops = NULL;
|
|
|
|
}
|