diff options
| author | allusive-dev <[email protected]> | 2023-10-30 15:12:21 +1100 |
|---|---|---|
| committer | allusive-dev <[email protected]> | 2023-10-30 15:12:21 +1100 |
| commit | ac33357e7ce7c474aeaffc92e381020289d767a2 (patch) | |
| tree | 7f05fa79b3ccd7834f85cc65a07fbd4f8030eb94 /src/backend/xrender | |
| parent | Create FUNDING.yml (diff) | |
| download | compfy-ac33357e7ce7c474aeaffc92e381020289d767a2.tar.xz compfy-ac33357e7ce7c474aeaffc92e381020289d767a2.zip | |
Version 1.01.0.0
Diffstat (limited to 'src/backend/xrender')
| -rw-r--r-- | src/backend/xrender/xrender.c | 382 |
1 files changed, 296 insertions, 86 deletions
diff --git a/src/backend/xrender/xrender.c b/src/backend/xrender/xrender.c index ccf358b..2b7f8e1 100644 --- a/src/backend/xrender/xrender.c +++ b/src/backend/xrender/xrender.c @@ -88,19 +88,153 @@ struct _xrender_image_data_inner { bool owned; }; -static void compose_impl(struct _xrender_data *xd, const struct backend_image *img, - int dst_x1, int dst_y1, int dst_x2, int dst_y2, - const region_t *reg_paint, const region_t *reg_visible, - xcb_render_picture_t result) { - auto alpha_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)]; +struct xrender_rounded_rectangle_cache { + int refcount; + // A cached picture of a rounded rectangle. Xorg rasterizes shapes on CPU so it's + // exceedingly slow. + xcb_render_picture_t p; +}; + +struct xrender_image { + struct backend_image base; + + struct xrender_rounded_rectangle_cache *rounded_rectangle; +}; + +/// Make a picture of size width x height, which has a rounded rectangle of corner_radius +/// rendered in it. +struct xrender_rounded_rectangle_cache * +make_rounded_corner_cache(xcb_connection_t *c, xcb_render_picture_t src, + xcb_drawable_t root, int width, int height, int corner_radius) { + auto picture = x_create_picture_with_standard(c, root, width, height, + XCB_PICT_STANDARD_ARGB_32, 0, NULL); + if (picture == XCB_NONE) { + return NULL; + } + + 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 + + XCB_AWAIT_VOID(xcb_render_tri_strip, c, XCB_RENDER_PICT_OP_SRC, src, picture, + x_get_pictfmt_for_standard(c, XCB_PICT_STANDARD_A_8), 0, 0, + (uint32_t)point_count, points); + free(points); + auto ret = ccalloc(1, struct xrender_rounded_rectangle_cache); + ret->p = picture; + ret->refcount = 1; + return ret; +} + +static xcb_render_picture_t process_mask(struct _xrender_data *xd, struct xrender_image *mask, + xcb_render_picture_t alpha_pict, bool *allocated) { + auto inner = (struct _xrender_image_data_inner *)mask->base.inner; + if (!mask->base.color_inverted && mask->base.corner_radius == 0) { + *allocated = false; + return inner->pict; + } + const auto tmpw = to_u16_checked(inner->width); + const auto tmph = to_u16_checked(inner->height); + *allocated = true; + x_clear_picture_clip_region(xd->base.c, inner->pict); + auto ret = x_create_picture_with_visual( + xd->base.c, xd->base.root, inner->width, inner->height, inner->visual, + XCB_RENDER_CP_REPEAT, + (xcb_render_create_picture_value_list_t[]){XCB_RENDER_REPEAT_PAD}); + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE, + ret, 0, 0, 0, 0, 0, 0, tmpw, tmph); + // Remember: the mask has a 1-pixel border + if (mask->base.corner_radius != 0) { + if (mask->rounded_rectangle == NULL) { + mask->rounded_rectangle = make_rounded_corner_cache( + xd->base.c, xd->white_pixel, xd->base.root, inner->width - 2, + inner->height - 2, (int)mask->base.corner_radius); + } + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, + mask->rounded_rectangle->p, XCB_NONE, ret, 0, 0, 0, + 0, 1, 1, (uint16_t)(tmpw - 2), (uint16_t)(tmph - 2)); + } + + if (mask->base.color_inverted) { + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_XOR, xd->white_pixel, + XCB_NONE, ret, 0, 0, 0, 0, 0, 0, tmpw, tmph); + } + + if (alpha_pict != XCB_NONE) { + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, ret, alpha_pict, + ret, 0, 0, 0, 0, 0, 0, to_u16_checked(inner->width), + to_u16_checked(inner->height)); + } + + return ret; +} + +static void +compose_impl(struct _xrender_data *xd, struct xrender_image *xrimg, coord_t dst, + struct xrender_image *mask, coord_t mask_dst, const region_t *reg_paint, + const region_t *reg_visible, xcb_render_picture_t result) { + const struct backend_image *img = &xrimg->base; + bool mask_allocated = false; + auto mask_pict = xd->alpha_pict[(int)(img->opacity * MAX_ALPHA)]; + if (mask != NULL) { + mask_pict = process_mask( + xd, mask, img->opacity < 1.0 ? mask_pict : XCB_NONE, &mask_allocated); + } auto inner = (struct _xrender_image_data_inner *)img->inner; region_t reg; bool has_alpha = inner->has_alpha || img->opacity != 1; - const auto tmpw = to_u16_checked(dst_x2 - dst_x1); - const auto tmph = to_u16_checked(dst_y2 - dst_y1); - const auto tmpew = to_u16_checked(dst_x2 - dst_x1); - const auto tmpeh = to_u16_checked(dst_y2 - dst_y1); + const auto tmpw = to_u16_checked(inner->width); + const auto tmph = to_u16_checked(inner->height); + const auto tmpew = to_u16_checked(img->ewidth); + const auto tmpeh = to_u16_checked(img->eheight); + // Remember: the mask has a 1-pixel border + const auto mask_dst_x = to_i16_checked(dst.x - mask_dst.x + 1); + const auto mask_dst_y = to_i16_checked(dst.y - mask_dst.y + 1); const xcb_render_color_t dim_color = { .red = 0, .green = 0, .blue = 0, .alpha = (uint16_t)(0xffff * img->dim)}; @@ -111,20 +245,12 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i pixman_region32_init(®); pixman_region32_intersect(®, (region_t *)reg_paint, (region_t *)reg_visible); x_set_picture_clip_region(xd->base.c, result, 0, 0, ®); - -#define DOUBLE_TO_XFIXED(value) ((xcb_render_fixed_t)(((double)(value)) * 65536)) - { - const xcb_render_transform_t transform = { - DOUBLE_TO_XFIXED((double)img->ewidth / (double)tmpew), DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(0.0), - DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED((double)img->eheight / (double)tmpeh), DOUBLE_TO_XFIXED(0.0), - DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(0.0), DOUBLE_TO_XFIXED(1.0), - }; - xcb_render_set_picture_transform(xd->base.c, inner->pict, transform); - xcb_render_set_picture_filter(xd->base.c, inner->pict, 7, "nearest", 0, NULL); - } -#undef DOUBLE_TO_XFIXED - - if ((img->color_inverted || img->dim != 0) && has_alpha) { + if (img->corner_radius != 0 && xrimg->rounded_rectangle == NULL) { + xrimg->rounded_rectangle = make_rounded_corner_cache( + xd->base.c, xd->white_pixel, xd->base.root, inner->width, + inner->height, (int)img->corner_radius); + } + 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. @@ -133,17 +259,25 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i inner->height, inner->visual, 0, NULL); // Set clip region translated to source coordinate - x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst_x1), - to_i16_checked(-dst_y1), ®); + x_set_picture_clip_region(xd->base.c, tmp_pict, to_i16_checked(-dst.x), + to_i16_checked(-dst.y), ®); // Copy source -> tmp - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, inner->pict, + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, inner->pict, XCB_NONE, tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); + + if (img->corner_radius != 0 && xrimg->rounded_rectangle != NULL) { + // Clip tmp_pict with a rounded rectangle + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_IN_REVERSE, + xrimg->rounded_rectangle->p, XCB_NONE, + tmp_pict, 0, 0, 0, 0, 0, 0, tmpw, tmph); + } + if (img->color_inverted) { if (inner->has_alpha) { auto tmp_pict2 = x_create_picture_with_visual( xd->base.c, xd->base.root, tmpw, tmph, inner->visual, 0, NULL); - xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, + xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_SRC, tmp_pict, XCB_NONE, tmp_pict2, 0, 0, 0, 0, 0, 0, tmpw, tmph); @@ -174,30 +308,31 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i } xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_OVER, tmp_pict, - alpha_pict, result, 0, 0, 0, 0, to_i16_checked(dst_x1), - to_i16_checked(dst_y1), tmpew, tmpeh); + mask_pict, result, 0, 0, mask_dst_x, mask_dst_y, + to_i16_checked(dst.x), to_i16_checked(dst.y), tmpew, + tmpeh); xcb_render_free_picture(xd->base.c, tmp_pict); } else { uint8_t op = (has_alpha ? XCB_RENDER_PICT_OP_OVER : XCB_RENDER_PICT_OP_SRC); - xcb_render_composite(xd->base.c, op, inner->pict, alpha_pict, result, 0, - 0, 0, 0, to_i16_checked(dst_x1), - to_i16_checked(dst_y1), tmpew, tmpeh); + xcb_render_composite(xd->base.c, op, inner->pict, mask_pict, result, 0, 0, + mask_dst_x, mask_dst_y, to_i16_checked(dst.x), + to_i16_checked(dst.y), tmpew, tmpeh); if (img->dim != 0 || img->color_inverted) { // Apply properties, if we reach here, then has_alpha == false assert(!has_alpha); if (img->color_inverted) { xcb_render_composite(xd->base.c, XCB_RENDER_PICT_OP_DIFFERENCE, xd->white_pixel, XCB_NONE, result, 0, - 0, 0, 0, to_i16_checked(dst_x1), - to_i16_checked(dst_y1), tmpew, tmpeh); + 0, 0, 0, to_i16_checked(dst.x), + to_i16_checked(dst.y), tmpew, tmpeh); } if (img->dim != 0) { // Dim the actually content of window xcb_rectangle_t rect = { - .x = to_i16_checked(dst_x1), - .y = to_i16_checked(dst_y1), + .x = to_i16_checked(dst.x), + .y = to_i16_checked(dst.y), .width = tmpew, .height = tmpeh, }; @@ -207,15 +342,17 @@ static void compose_impl(struct _xrender_data *xd, const struct backend_image *i } } } + if (mask_allocated) { + xcb_render_free_picture(xd->base.c, mask_pict); + } pixman_region32_fini(®); } -static void compose(backend_t *base, void *img_data, - int dst_x1, int dst_y1, int dst_x2, int dst_y2, +static void compose(backend_t *base, void *img_data, coord_t dst, void *mask, coord_t mask_dst, const region_t *reg_paint, const region_t *reg_visible) { - // TODO(dccsillag): use dst_{x,y}2 struct _xrender_data *xd = (void *)base; - return compose_impl(xd, img_data, dst_x1, dst_y1, dst_x2, dst_y2, reg_paint, reg_visible, xd->back[2]); + return compose_impl(xd, img_data, dst, mask, mask_dst, reg_paint, reg_visible, + xd->back[2]); } static void fill(backend_t *base, struct color c, const region_t *clip) { @@ -236,8 +373,8 @@ static void fill(backend_t *base, struct color c, const region_t *clip) { .height = to_u16_checked(extent->y2 - extent->y1)}}); } -static bool blur(backend_t *backend_data, double opacity, void *ctx_, - const region_t *reg_blur, const region_t *reg_visible) { +static bool blur(backend_t *backend_data, double opacity, void *ctx_, void *mask, + coord_t mask_dst, const region_t *reg_blur, const region_t *reg_visible) { struct _xrender_blur_context *bctx = ctx_; if (bctx->method == BLUR_METHOD_NONE) { return true; @@ -288,7 +425,12 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, pixman_region32_fini(&clip); xcb_render_picture_t src_pict = xd->back[2], dst_pict = tmp_picture[0]; - auto alpha_pict = xd->alpha_pict[(int)(opacity * MAX_ALPHA)]; + auto mask_pict = xd->alpha_pict[(int)(opacity * MAX_ALPHA)]; + bool mask_allocated = false; + if (mask != NULL) { + mask_pict = process_mask(xd, mask, opacity != 1.0 ? mask_pict : XCB_NONE, + &mask_allocated); + } int current = 0; x_set_picture_clip_region(c, src_pict, 0, 0, ®_op_resized); @@ -324,11 +466,12 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, } else { x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op); // This is the last pass, and we are doing more than 1 pass - xcb_render_composite(c, XCB_RENDER_PICT_OP_OVER, src_pict, - alpha_pict, xd->back[2], 0, 0, 0, 0, - to_i16_checked(extent_resized->x1), - to_i16_checked(extent_resized->y1), - width_resized, height_resized); + xcb_render_composite( + c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2], + 0, 0, to_i16_checked(extent_resized->x1 - mask_dst.x + 1), + to_i16_checked(extent_resized->y1 - mask_dst.y + 1), + to_i16_checked(extent_resized->x1), + to_i16_checked(extent_resized->y1), width_resized, height_resized); } // reset filter @@ -344,8 +487,10 @@ static bool blur(backend_t *backend_data, double opacity, void *ctx_, if (i == 1) { x_set_picture_clip_region(c, xd->back[2], 0, 0, ®_op); xcb_render_composite( - c, XCB_RENDER_PICT_OP_OVER, src_pict, alpha_pict, xd->back[2], 0, 0, - 0, 0, to_i16_checked(extent_resized->x1), + c, XCB_RENDER_PICT_OP_OVER, src_pict, mask_pict, xd->back[2], 0, 0, + to_i16_checked(extent_resized->x1 - mask_dst.x + 1), + to_i16_checked(extent_resized->y1 - mask_dst.y + 1), + to_i16_checked(extent_resized->x1), to_i16_checked(extent_resized->y1), width_resized, height_resized); } @@ -366,11 +511,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 = @@ -379,8 +524,9 @@ 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 = NULL; free(r); if (inner->pict == XCB_NONE) { @@ -397,11 +543,28 @@ static void release_image_inner(backend_t *base, struct _xrender_image_data_inne } free(inner); } + +static void +release_rounded_corner_cache(backend_t *base, struct xrender_rounded_rectangle_cache *cache) { + if (!cache) { + return; + } + + assert(cache->refcount > 0); + cache->refcount--; + if (cache->refcount == 0) { + xcb_free_pixmap(base->c, cache->p); + free(cache); + } +} + 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; + release_rounded_corner_cache(base, img->rounded_rectangle); + img->rounded_rectangle = NULL; + img->base.inner->refcount -= 1; + if (img->base.inner->refcount == 0) { + release_image_inner(base, (void *)img->base.inner); } free(img); } @@ -523,6 +686,52 @@ new_inner(backend_t *base, int w, int h, xcb_visualid_t visual, uint8_t depth) { return new_inner; } +static void *make_mask(backend_t *base, geometry_t size, const region_t *reg) { + struct _xrender_data *xd = (void *)base; + // Give the mask a 1 pixel wide border to emulate the clamp to border behavior of + // OpenGL textures. + auto w16 = to_u16_checked(size.width + 2); + auto h16 = to_u16_checked(size.height + 2); + auto inner = + new_inner(base, size.width + 2, size.height + 2, + x_get_visual_for_standard(base->c, XCB_PICT_STANDARD_ARGB_32), 32); + xcb_render_change_picture(base->c, inner->pict, XCB_RENDER_CP_REPEAT, + (uint32_t[]){XCB_RENDER_REPEAT_PAD}); + const rect_t *extent = pixman_region32_extents((region_t *)reg); + x_set_picture_clip_region(base->c, xd->back[2], 1, 1, reg); + xcb_render_fill_rectangles( + base->c, XCB_RENDER_PICT_OP_SRC, inner->pict, + (xcb_render_color_t){.red = 0, .green = 0, .blue = 0, .alpha = 0xffff}, 1, + (xcb_rectangle_t[]){{.x = to_i16_checked(extent->x1 + 1), + .y = to_i16_checked(extent->y1 + 1), + .width = to_u16_checked(extent->x2 - extent->x1), + .height = to_u16_checked(extent->y2 - extent->y1)}}); + x_clear_picture_clip_region(xd->base.c, inner->pict); + + // Paint the border transparent + xcb_render_fill_rectangles( + base->c, XCB_RENDER_PICT_OP_SRC, inner->pict, + (xcb_render_color_t){.red = 0, .green = 0, .blue = 0, .alpha = 0}, 4, + (xcb_rectangle_t[]){{.x = 0, .y = 0, .width = w16, .height = 1}, + {.x = 0, .y = 0, .width = 1, .height = h16}, + {.x = 0, .y = (short)(h16 - 1), .width = w16, .height = 1}, + {.x = (short)(w16 - 1), .y = 0, .width = 1, .height = h16}}); + inner->refcount = 1; + + auto img = ccalloc(1, struct xrender_image); + img->base.eheight = size.height + 2; + img->base.ewidth = size.width + 2; + img->base.border_width = 0; + img->base.color_inverted = false; + img->base.corner_radius = 0; + img->base.max_brightness = 1; + img->base.opacity = 1; + img->base.dim = 0; + img->base.inner = (struct backend_image_inner_base *)inner; + img->rounded_rectangle = NULL; + return img; +} + static bool decouple_image(backend_t *base, struct backend_image *img, const region_t *reg) { if (img->inner->refcount == 1) { return true; @@ -648,30 +857,6 @@ static void get_blur_size(void *blur_context, int *width, int *height) { *height = ctx->resize_height; } -static bool -read_pixel(backend_t *backend_data, void *image_data, int x, int y, struct color *output) { - auto xd = (struct _xrender_data *)backend_data; - auto img = (struct backend_image *)image_data; - auto inner = (struct _xrender_image_data_inner *)img->inner; - - auto r = XCB_AWAIT(xcb_get_image, xd->base.c, XCB_IMAGE_FORMAT_XY_PIXMAP, inner->pixmap, - to_i16_checked(x), to_i16_checked(y), 1, 1, (uint32_t)-1L); - - if (!r) { - return false; - } - - // Color format seems to be BGRA8888, see glamor_format_for_pixmap from the - // Xserver codebase. - uint8_t *pixels = xcb_get_image_data(r); - output->blue = pixels[0] / 255.0; - output->green = pixels[1] / 255.0; - output->red = pixels[2] / 255.0; - output->alpha = pixels[3] / 255.0; - - return true; -} - static backend_t *backend_xrender_init(session_t *ps) { auto xd = ccalloc(1, struct _xrender_data); init_backend_base(&xd->base, ps); @@ -751,6 +936,29 @@ err: return NULL; } +void *clone_image(backend_t *base attr_unused, const void *image_data, + const region_t *reg_visible attr_unused) { + auto new_img = ccalloc(1, struct xrender_image); + *new_img = *(struct xrender_image *)image_data; + new_img->base.inner->refcount++; + if (new_img->rounded_rectangle) { + new_img->rounded_rectangle->refcount++; + } + return new_img; +} + +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) { + // Free cached rounded rectangle if corner radius changed + release_rounded_corner_cache(base, xrimg->rounded_rectangle); + xrimg->rounded_rectangle = NULL; + } + return default_set_image_property(base, op, image, args); +} + struct backend_operations xrender_ops = { .init = backend_xrender_init, .deinit = deinit, @@ -760,7 +968,10 @@ struct backend_operations xrender_ops = { .fill = fill, .bind_pixmap = bind_pixmap, .release_image = release_image, + .create_shadow_context = default_create_shadow_context, + .destroy_shadow_context = default_destroy_shadow_context, .render_shadow = default_backend_render_shadow, + .make_mask = make_mask, //.prepare_win = prepare_win, //.release_win = release_win, .is_image_transparent = default_is_image_transparent, @@ -768,9 +979,8 @@ struct backend_operations xrender_ops = { .max_buffer_age = 2, .image_op = image_op, - .read_pixel = read_pixel, - .clone_image = default_clone_image, - .set_image_property = default_set_image_property, + .clone_image = clone_image, + .set_image_property = set_image_property, .create_blur_context = create_blur_context, .destroy_blur_context = destroy_blur_context, .get_blur_size = get_blur_size, |