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:
parent
9dd075be52
commit
6e962470b0
11 changed files with 106 additions and 22 deletions
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 &&
|
||||
|
|
|
@ -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)
|
||||
|
|
10
src/wm/win.c
10
src/wm/win.c
|
@ -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;
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue