1
0
Fork 0
mirror of https://github.com/yshui/picom.git synced 2024-11-03 04:33:49 -05:00

animation: add saved-image-blend

Allow animation to blend in saved window image before it was refresh.
Window images are refreshed when, for example, the window's size
changed. With this, animations can blend the window before and after the
size change to have a smoother transition.

Signed-off-by: Yuxuan Shui <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2024-08-12 02:56:22 +01:00
parent 9dd075be52
commit 6e962470b0
No known key found for this signature in database
GPG key ID: D3A4405BE6CC17F4
11 changed files with 106 additions and 22 deletions

View file

@ -183,6 +183,7 @@ enum backend_command_op {
/// will later be filled in by the renderer using this symbolic reference.
enum backend_command_source {
BACKEND_COMMAND_SOURCE_WINDOW,
BACKEND_COMMAND_SOURCE_WINDOW_SAVED,
BACKEND_COMMAND_SOURCE_SHADOW,
BACKEND_COMMAND_SOURCE_BACKGROUND,
};

View file

@ -697,6 +697,10 @@ Currently, these output variables are supported: :::
_crop-x_, _crop-y_, _crop-width_, _crop-height_:: These four values combined defines a rectangle on the screen. The window and its shadow will be cropped to this rectangle. If not defined, the window and shadow will not be cropped.
_saved-image-blend_:: When the window's geometry changes, its content will often change drastically, creating a jarring discontinuity. This output variable allows you to blend the window's content before and after the geometry change, the before and after images will be stretched appropriately to match the animation. This way you can smoothly animated geometry changes. This is a number between 0 and 1. 0 means the saved image is not used, whereas 1 means you will only see the saved image. (EXPERIMENTAL)
+
WARNING: The _saved-image-blend_ variable is experimental. It might work incorrectly, cause visual artifacts, or slow down your system. You are welcome to open an issue on GitHub if you encounter any problems to help us improve it, though resolution is not guaranteed.
All coordinates are in pixels, and are in the coordinate system of the screen. Sizes are also in pixels.
IMPORTANT: If an output variable name is not defined in your animation script, it will take the default value for whichever state the window is in. Specifically, if you don't define an _opacity_ variable in the animation script for the "close" or "hide" trigger, a closed window will, by default, have 0 opacity. So you will just see it disappear instantly. Oftentimes, you will want to set _opacity_ to 1 to make the window visible for the duration of the animation.

View file

@ -11,6 +11,7 @@
#include "config.h"
#include "log.h"
#include "region.h"
#include "renderer/layout.h"
#include "wm/win.h"
#include "x.h"
@ -121,6 +122,7 @@ bool backend_execute(struct backend_base *backend, image_handle target, unsigned
static inline const char *render_command_source_name(enum backend_command_source source) {
switch (source) {
case BACKEND_COMMAND_SOURCE_WINDOW: return "window";
case BACKEND_COMMAND_SOURCE_WINDOW_SAVED: return "window_saved";
case BACKEND_COMMAND_SOURCE_SHADOW: return "shadow";
case BACKEND_COMMAND_SOURCE_BACKGROUND: return "background";
}

View file

@ -15,10 +15,11 @@
/// it's stored in `cmd` going backwards, i.e. cmd - 1, -2, ...
/// @return number of commands generated
static inline unsigned
commands_for_window_body(struct layer *layer, struct backend_command *cmd,
commands_for_window_body(struct layer *layer, struct backend_command *cmd_base,
const region_t *frame_region, bool inactive_dim_fixed,
double max_brightness) {
auto w = layer->win;
auto cmd = cmd_base;
scoped_region_t crop = region_from_box(layer->crop);
auto mode = win_calc_mode_raw(layer->win);
int border_width = w->g.border_width;
@ -48,18 +49,20 @@ commands_for_window_body(struct layer *layer, struct backend_command *cmd,
if (layer->options.corner_radius > 0) {
win_region_remove_corners(w, layer->window.origin, &cmd->opaque_region);
}
region_scale(&cmd->target_mask, layer->window.origin, layer->scale);
region_scale(&cmd->opaque_region, layer->window.origin, layer->scale);
pixman_region32_intersect(&cmd->target_mask, &cmd->target_mask, &crop);
pixman_region32_intersect(&cmd->opaque_region, &cmd->opaque_region, &crop);
cmd->op = BACKEND_COMMAND_BLIT;
cmd->source = BACKEND_COMMAND_SOURCE_WINDOW;
cmd->origin = layer->window.origin;
cmd->blit = (struct backend_blit_args){
float opacity = layer->opacity * (1 - layer->saved_image_blend);
if (opacity > (1. - 1. / MAX_ALPHA)) {
// Avoid division by a very small number
opacity = 1;
}
float opacity_saved = 0;
if (opacity < 1) {
opacity_saved = layer->opacity * layer->saved_image_blend / (1 - opacity);
}
struct backend_blit_args args_base = {
.border_width = border_width,
.target_mask = &cmd->target_mask,
.corner_radius = layer->options.corner_radius,
.opacity = layer->opacity,
.opacity = opacity,
.dim = dim,
.scale = layer->scale,
.effective_size = layer->window.size,
@ -68,11 +71,37 @@ commands_for_window_body(struct layer *layer, struct backend_command *cmd,
.source_mask = NULL,
.max_brightness = max_brightness,
};
region_scale(&cmd->target_mask, layer->window.origin, layer->scale);
region_scale(&cmd->opaque_region, layer->window.origin, layer->scale);
pixman_region32_intersect(&cmd->target_mask, &cmd->target_mask, &crop);
pixman_region32_intersect(&cmd->opaque_region, &cmd->opaque_region, &crop);
cmd->op = BACKEND_COMMAND_BLIT;
cmd->source = BACKEND_COMMAND_SOURCE_WINDOW;
cmd->origin = layer->window.origin;
cmd->blit = args_base;
cmd->blit.target_mask = &cmd->target_mask;
cmd -= 1;
if (layer->saved_image_blend > 0) {
pixman_region32_copy(&cmd->target_mask, &cmd[1].target_mask);
cmd->opaque_region = cmd[1].opaque_region;
pixman_region32_init(&cmd[1].opaque_region);
cmd->op = BACKEND_COMMAND_BLIT;
cmd->source = BACKEND_COMMAND_SOURCE_WINDOW_SAVED;
cmd->origin = layer->window.origin;
cmd->blit = args_base;
cmd->blit.effective_size = (ivec2){
.width = (int)(layer->window.size.width / w->saved_win_image_scale.width),
.height = (int)(layer->window.size.height / w->saved_win_image_scale.height),
};
cmd->blit.opacity = opacity_saved;
cmd->blit.target_mask = &cmd->target_mask;
cmd->blit.scale = vec2_scale(cmd->blit.scale, w->saved_win_image_scale);
cmd -= 1;
}
if (w->frame_opacity == 1 || w->frame_opacity == 0) {
return 1;
return (unsigned)(cmd_base - cmd);
}
cmd -= 1;
pixman_region32_copy(&cmd->target_mask, frame_region);
region_scale(&cmd->target_mask, cmd->origin, layer->scale);
@ -81,10 +110,27 @@ commands_for_window_body(struct layer *layer, struct backend_command *cmd,
cmd->op = BACKEND_COMMAND_BLIT;
cmd->origin = layer->window.origin;
cmd->source = BACKEND_COMMAND_SOURCE_WINDOW;
cmd->blit = cmd[1].blit;
cmd->blit = args_base;
cmd->blit.target_mask = &cmd->target_mask;
cmd->blit.opacity *= w->frame_opacity;
return 2;
cmd->blit.opacity = w->frame_opacity * opacity;
cmd -= 1;
if (layer->saved_image_blend > 0) {
pixman_region32_copy(&cmd->target_mask, &cmd[1].target_mask);
pixman_region32_init(&cmd->opaque_region);
cmd->op = BACKEND_COMMAND_BLIT;
cmd->source = BACKEND_COMMAND_SOURCE_WINDOW_SAVED;
cmd->origin = layer->window.origin;
cmd->blit = args_base;
cmd->blit.effective_size = (ivec2){
.width = (int)(layer->window.size.width / w->saved_win_image_scale.width),
.height = (int)(layer->window.size.height / w->saved_win_image_scale.height),
};
cmd->blit.opacity = w->frame_opacity * opacity_saved;
cmd->blit.target_mask = &cmd->target_mask;
cmd->blit.scale = vec2_scale(cmd->blit.scale, w->saved_win_image_scale);
cmd -= 1;
}
return (unsigned)(cmd_base - cmd);
}
/// Generate render command for the shadow in `layer`
@ -115,7 +161,8 @@ command_for_shadow(struct layer *layer, struct backend_command *cmd,
// should be blits for the current window.
for (auto j = cmd + 1; j != end; j++) {
assert(j->op == BACKEND_COMMAND_BLIT);
assert(j->source == BACKEND_COMMAND_SOURCE_WINDOW);
assert(j->source == BACKEND_COMMAND_SOURCE_WINDOW ||
j->source == BACKEND_COMMAND_SOURCE_WINDOW_SAVED);
if (j->blit.corner_radius == 0) {
pixman_region32_subtract(
&cmd->target_mask, &cmd->target_mask, &j->target_mask);
@ -375,11 +422,16 @@ void command_builder_build(struct command_builder *cb, struct layout *layout,
if (layer->options.shadow) {
ncmds += 1;
}
unsigned n_cmds_for_window_body = 1;
if (layer->win->frame_opacity < 1 && layer->win->frame_opacity > 0) {
// Needs to draw the frame separately
ncmds += 1;
n_cmds_for_window_body += 1;
}
ncmds += 1; // window body
if (layer->saved_image_blend > 0) {
n_cmds_for_window_body *= 2;
}
ncmds += n_cmds_for_window_body; // window body
}
auto list = command_builder_command_list_new(cb, ncmds);

View file

@ -30,6 +30,10 @@ layer_compare(const struct layer *past_layer, const struct backend_command *past
// Shadow moved or size changed
return false;
}
if (past_layer->saved_image_blend != curr_layer->saved_image_blend) {
// The amount of blending with the saved image changed
return false;
}
if (past_layer->number_of_commands != curr_layer->number_of_commands) {
// Number of render commands changed. We are being conservative
// here, because even though the number of commands changed, we can still

View file

@ -101,6 +101,12 @@ static bool layer_from_window(struct layer *out_layer, struct win *w, ivec2 size
goto out;
}
out_layer->saved_image_blend =
(float)win_animatable_get(w, WIN_SCRIPT_SAVED_IMAGE_BLEND);
if (w->saved_win_image == NULL) {
out_layer->saved_image_blend = 0;
}
pixman_region32_copy(&out_layer->damaged, &w->damaged);
pixman_region32_translate(&out_layer->damaged, out_layer->window.origin.x,
out_layer->window.origin.y);

View file

@ -38,6 +38,8 @@ struct layer {
float blur_opacity;
/// Opacity of this window's shadow
float shadow_opacity;
/// How much the image of this window should be blended with the saved image
float saved_image_blend;
/// Crop the content of this layer to this box, in screen coordinates.
struct ibox crop;

View file

@ -416,6 +416,9 @@ static bool renderer_prepare_commands(struct renderer *r, struct backend_base *b
} else if (cmd->source == BACKEND_COMMAND_SOURCE_WINDOW) {
assert(w->win_image);
cmd->blit.source_image = w->win_image;
} else if (cmd->source == BACKEND_COMMAND_SOURCE_WINDOW_SAVED) {
assert(w->saved_win_image);
cmd->blit.source_image = w->saved_win_image;
}
if (cmd->blit.source_mask != NULL) {
if (w->mask_image == NULL &&

View file

@ -101,5 +101,8 @@ enum win_script_output {
WIN_SCRIPT_CROP_WIDTH,
/// Height of the crop box
WIN_SCRIPT_CROP_HEIGHT,
/// How much to blend in the saved window image
WIN_SCRIPT_SAVED_IMAGE_BLEND,
NUM_OF_WIN_SCRIPT_OUTPUTS,
};
#define NUM_OF_WIN_SCRIPT_OUTPUTS (WIN_SCRIPT_CROP_HEIGHT + 1)

View file

@ -1767,6 +1767,8 @@ double win_animatable_get(const struct win *w, enum win_script_output output) {
case WIN_SCRIPT_SHADOW_SCALE_Y: return 1;
case WIN_SCRIPT_CROP_WIDTH:
case WIN_SCRIPT_CROP_HEIGHT: return INFINITY;
case WIN_SCRIPT_SAVED_IMAGE_BLEND: return 0;
default: unreachable();
}
unreachable();
}
@ -1948,13 +1950,17 @@ bool win_process_animation_and_state_change(struct session *ps, struct win *w, d
// old has left off. Note we still need to advance the old animation for
// the last interval.
win_advance_animation(w, delta_t, &win_ctx);
auto memory = w->running_animation_instance->memory;
auto output_indices = w->running_animation.output_indices;
if (output_indices[WIN_SCRIPT_SAVED_IMAGE_BLEND] >= 0) {
memory[output_indices[WIN_SCRIPT_SAVED_IMAGE_BLEND]] =
1 - memory[output_indices[WIN_SCRIPT_SAVED_IMAGE_BLEND]];
}
if (geometry_changed) {
// If the window has moved, we need to adjust scripts
// outputs so that the window will stay in the same position and
// size after applying the animation. This way the window's size
// and position won't change discontinuously.
auto memory = w->running_animation_instance->memory;
auto output_indices = w->running_animation.output_indices;
struct {
int output;
double delta;

View file

@ -313,6 +313,7 @@ static const struct script_output_info win_script_outputs[] = {
[WIN_SCRIPT_CROP_Y] = {"crop-y"},
[WIN_SCRIPT_CROP_WIDTH] = {"crop-width"},
[WIN_SCRIPT_CROP_HEIGHT] = {"crop-height"},
[WIN_SCRIPT_SAVED_IMAGE_BLEND] = {"saved-image-blend"},
[NUM_OF_WIN_SCRIPT_OUTPUTS] = {NULL},
};