backend: xrender: handle corner radius

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2021-10-21 18:19:23 +01:00
parent d397b307dd
commit 76db8cca06
No known key found for this signature in database
GPG Key ID: D3A4405BE6CC17F4
3 changed files with 114 additions and 17 deletions

View File

@ -88,9 +88,74 @@ struct _xrender_image_data_inner {
bool owned;
};
static void compose_impl(struct _xrender_data *xd, const struct backend_image *img,
int dst_x, int dst_y, const region_t *reg_paint,
struct xrender_image {
struct backend_image base;
// A triangulated rounded rectangle
xcb_render_pointfix_t *rounded_rectangle_triangles;
// Number of points in the above list of triangles
int triangle_count;
};
/// Make a picture of size width x height, which has a rounded rectangle of corner_radius
/// rendered in it.
xcb_render_pointfix_t *
make_rounded_corner_points(int width, int height, int corner_radius, int *out_point_count) {
int inner_height = height - 2 * corner_radius;
int cap_height = corner_radius;
if (inner_height < 0) {
cap_height = height / 2;
inner_height = 0;
}
auto points = ccalloc(cap_height * 4 + 4, xcb_render_pointfix_t);
int point_count = 0;
#define ADD_POINT(px, py) \
assert(point_count < cap_height * 4 + 4); \
points[point_count].x = DOUBLE_TO_XFIXED(px); \
points[point_count].y = DOUBLE_TO_XFIXED(py); \
point_count += 1;
// The top cap
for (int i = 0; i <= cap_height; i++) {
double y = corner_radius - i;
double delta = sqrt(corner_radius * corner_radius - y * y);
double left = corner_radius - delta;
double right = width - corner_radius + delta;
if (left >= right) {
continue;
}
ADD_POINT(left, i);
ADD_POINT(right, i);
}
// The middle rectangle
if (inner_height > 0) {
ADD_POINT(0, cap_height + inner_height);
ADD_POINT(width, cap_height + inner_height);
}
// The bottom cap
for (int i = cap_height + inner_height + 1; i <= height; i++) {
double y = corner_radius - (height - i);
double delta = sqrt(corner_radius * corner_radius - y * y);
double left = corner_radius - delta;
double right = width - corner_radius + delta;
if (left >= right) {
break;
}
ADD_POINT(left, i);
ADD_POINT(right, i);
}
#undef ADD_POINT
*out_point_count = point_count;
return points;
}
static void compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, int dst_x,
int dst_y, const region_t *reg_paint,
const region_t *reg_visible, xcb_render_picture_t result) {
const struct backend_image *img = &xrimg->base;
auto alpha_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)];
auto inner = (struct _xrender_image_data_inner *)img->inner;
region_t reg;
@ -110,7 +175,12 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i
pixman_region32_init(&reg);
pixman_region32_intersect(&reg, (region_t *)reg_paint, (region_t *)reg_visible);
x_set_picture_clip_region(xd->base.c, result, 0, 0, &reg);
if ((img->color_inverted || img->dim != 0) && has_alpha) {
if (img->corner_radius != 0 && xrimg->rounded_rectangle_triangles == NULL) {
xrimg->rounded_rectangle_triangles = make_rounded_corner_points(
inner->width, inner->height, (int)img->corner_radius,
&xrimg->triangle_count);
}
if (((img->color_inverted || img->dim != 0) && has_alpha) || img->corner_radius != 0) {
// Apply image properties using a temporary image, because the source
// image is transparent. Otherwise the properties can be applied directly
// on the target image.
@ -159,6 +229,15 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i
tmp_pict, dim_color, 1, &rect);
}
if (img->corner_radius != 0) {
// Clip tmp_pict with a rounded rectangle
xcb_render_tri_strip(
xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, xd->white_pixel, tmp_pict,
x_get_pictfmt_for_standard(xd->base.c, XCB_PICT_STANDARD_A_8),
0, 0, (uint32_t)xrimg->triangle_count,
xrimg->rounded_rectangle_triangles);
}
xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, tmp_pict,
alpha_pict, result, 0, 0, 0, 0, to_i16_checked(dst_x),
to_i16_checked(dst_y), tmpew, tmpeh);
@ -350,11 +429,11 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool
return NULL;
}
auto img = ccalloc(1, struct backend_image);
auto img = ccalloc(1, struct xrender_image);
auto inner = ccalloc(1, struct _xrender_image_data_inner);
inner->depth = (uint8_t)fmt.visual_depth;
inner->width = img->ewidth = r->width;
inner->height = img->eheight = r->height;
inner->width = img->base.ewidth = r->width;
inner->height = img->base.eheight = r->height;
inner->pixmap = pixmap;
inner->has_alpha = fmt.alpha_size != 0;
inner->pict =
@ -363,8 +442,10 @@ bind_pixmap(backend_t *base, xcb_pixmap_t pixmap, struct xvisual_info fmt, bool
inner->visual = fmt.visual;
inner->refcount = 1;
img->inner = (struct backend_image_inner_base *)inner;
img->opacity = 1;
img->base.inner = (struct backend_image_inner_base *)inner;
img->base.opacity = 1;
img->rounded_rectangle_triangles = NULL;
img->triangle_count = 0;
free(r);
if (inner->pict == XCB_NONE) {
@ -382,10 +463,12 @@ static void release_image_inner(backend_t *base, struct _xrender_image_data_inne
free(inner);
}
static void release_image(backend_t *base, void *image) {
struct backend_image *img = image;
img->inner->refcount--;
if (img->inner->refcount == 0) {
release_image_inner(base, (void *)img->inner);
struct xrender_image *img = image;
free(img->rounded_rectangle_triangles);
img->triangle_count = 0;
img->base.inner->refcount--;
if (img->base.inner->refcount == 0) {
release_image_inner(base, (void *)img->base.inner);
}
free(img);
}
@ -735,6 +818,20 @@ err:
return NULL;
}
static bool
set_image_property(backend_t *base, enum image_properties op, void *image, void *args) {
auto xrimg = (struct xrender_image *)image;
if (op == IMAGE_PROPERTY_CORNER_RADIUS &&
((double *)args)[0] != xrimg->base.corner_radius &&
xrimg->rounded_rectangle_triangles != NULL) {
// Free cached rounded rectangle if corner radius changed
free(xrimg->rounded_rectangle_triangles);
xrimg->triangle_count = 0;
xrimg->rounded_rectangle_triangles = NULL;
}
return default_set_image_property(base, op, image, args);
}
struct backend_operations xrender_ops = {
.init = backend_xrender_init,
.deinit = deinit,
@ -754,7 +851,7 @@ struct backend_operations xrender_ops = {
.image_op = image_op,
.read_pixel = read_pixel,
.clone_image = default_clone_image,
.set_image_property = default_set_image_property,
.set_image_property = set_image_property,
.create_blur_context = create_blur_context,
.destroy_blur_context = destroy_blur_context,
.get_blur_size = get_blur_size,

View File

@ -652,10 +652,6 @@ err:
return false;
}
// xcb-render specific macros
#define XFIXED_TO_DOUBLE(value) (((double)(value)) / 65536)
#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536))
/**
* Convert a struct conv to a X picture convolution filter, normalizing the kernel
* in the process. Allow the caller to specify the element at the center of the kernel,

View File

@ -87,6 +87,10 @@ struct xvisual_info {
#define log_fatal_x_error(e, fmt, ...) \
LOG(FATAL, fmt " (%s)", ##__VA_ARGS__, x_strerror(e))
// xcb-render specific macros
#define XFIXED_TO_DOUBLE(value) (((double)(value)) / 65536)
#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536))
/// Wraps x_new_id. abort the program if x_new_id returns error
static inline uint32_t x_new_id(xcb_connection_t *c) {
auto ret = xcb_generate_id(c);