Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Render drop shadow for active window #58

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

weihsinyeh
Copy link
Collaborator

@weihsinyeh weihsinyeh commented Oct 6, 2024

Use Pascal's Triangle to approximate a 5x5 Gaussian Kernel, and apply Gaussian blur on the twin_pixmap_t to implement the blurring technique.

See: #34

include/twin.h Outdated Show resolved Hide resolved
src/draw.c Outdated Show resolved Hide resolved
src/draw.c Outdated Show resolved Hide resolved
@weihsinyeh
Copy link
Collaborator Author

weihsinyeh commented Oct 6, 2024

The idea behind #34 is to implement drop shadow, one of the most popular visual effects in modern window systems and compositing window managers. The effect should be applied at the window management level, not to background images, meaning the background image should not be blurred. The graphical effect of a visually elevated shape with a shadow underneath the window frame should occur transparently.

Because I'm still unsure where to place the blur operation, whether to implement the blur in '_twin_widget_queue_paint()', as this function handles the rendering order of each widget object. It would need to read the corresponding pixel block currently on the screen during rendering to blur the current background. Or should I implement this operation when each widget's TwinEventPaint occurs? So I first try to implement this function on the background to see its effect.

@jserv
Copy link
Contributor

jserv commented Oct 6, 2024

Or should I implement this operation when each widget's TwinEventPaint occurs? So I first try to implement this function on the background to see its effect.

Alternatively, you can write an image viewer with drop shadow functionality to evaluate performance and visual effects in the initial stage. Once you have a better understanding of the window manager events and their flow, we can proceed with integrating it into the window system.

See MDN: drop-shadow

@jserv
Copy link
Contributor

jserv commented Oct 6, 2024

Alternatively, you can write an image viewer with drop shadow functionality to evaluate performance and visual effects in the initial stage.

For performance evaluation, you may consider the code mentioned in #55 (comment) .

@weihsinyeh
Copy link
Collaborator Author

I haven't applied stack blur to the drop shadow yet, because I don't know how to access the current screen's topmost pixel map. I want to apply the stack blur to a specific region of the topmost pixel map that overlaps with the shadow and then use this as the new shadow. The topmost pixel map is efficiently updated by refreshing only the damaged areas.

apps/multi.c Outdated Show resolved Hide resolved
@weihsinyeh weihsinyeh force-pushed the drop_shadow branch 2 times, most recently from a78fc51 to 3433384 Compare November 13, 2024 13:44
@jserv
Copy link
Contributor

jserv commented Nov 13, 2024

Compilation error/warning reported by Clang:

src/window.c:72:13: error: non-void function 'twin_window_create' should return a value [-Wreturn-type]
   72 |             return;
      |             ^
src/window.c:65:17: warning: left operand of comma operator has no effect [-Wunused-value]
   65 |         window->shadow_offset_x, window->shadow_offset_y = 50, 50;
      |         ~~~~~~  ^~~~~~~~~~~~~~~
src/window.c:65:64: warning: expression result unused [-Wunused-value]
   65 |         window->shadow_offset_x, window->shadow_offset_y = 50, 50;
      |                    

@jserv
Copy link
Contributor

jserv commented Nov 13, 2024

I am unsure how to validate this. Could you provide the relevant procedures or methods along with the expected behavior?

@jserv
Copy link
Contributor

jserv commented Jan 2, 2025

The blur appeared too strong and spread out compared to typical drop shadow effects found in compositing window managers. Would you adjust the Gaussian kernel to create a tighter, more natural-looking shadow?

@weihsinyeh
Copy link
Collaborator Author

Do you want to directly reduce kernel size or use the gradient method to reduce kernel size?

@jserv
Copy link
Contributor

jserv commented Jan 3, 2025

Do you want to directly reduce kernel size or use the gradient method to reduce kernel size?

For window compositing, the blur effect should only be applied to the window decorator when creating drop shadows, not to the entire window. The kernel size shrinks.

@weihsinyeh
Copy link
Collaborator Author

Do all windows need to have the drop shadow feature? And only the window on the top layer will display the drop shadow.

@jserv
Copy link
Contributor

jserv commented Jan 3, 2025

Do all windows need to have the drop shadow feature? And only the window on the top layer will display the drop shadow.

Blur effects are only applied to the active window, using drop shadow to make it visually distinct.

@jserv
Copy link
Contributor

jserv commented Jan 4, 2025

It looks much better now.
blur

The latest update enforces blur effects on the active window, matching the standard behavior of compositing window managers. To enhance the visual depth, we should replace the right and bottom window borders with drop shadows, creating darker edges that give a more dimensional appearance. This means disabling the standard border rendering on those sides and implementing drop shadow effects instead.

Reference video: Apply Blur to xfce 4 Desktop Manager
picom is a compositor for X, and a fork of Compton.

@weihsinyeh
Copy link
Collaborator Author

This is the version that directly applies the shadow.
image
This is the version where the shadow has a gradient effect.
image
Is "Replace the right and bottom window borders with drop shadows, creating darker edges that give a more dimensional appearance" referring to one of these two?

@jserv
Copy link
Contributor

jserv commented Jan 4, 2025

Is "Replace the right and bottom window borders with drop shadows, creating darker edges that give a more dimensional appearance" referring to one of these two?

Yes, exactly.

@jserv jserv changed the title Add the Gaussian function to blur a pixmap Render drop show for active window Jan 4, 2025
@jserv jserv changed the title Render drop show for active window Render drop shadow for active window Jan 4, 2025
@jserv
Copy link
Contributor

jserv commented Jan 4, 2025

drop-shadow

The shadow should be aligned with the right and bottom borders, meaning the right shadow should be positioned at the top-right corner of the active window, while the bottom shadow should be placed at the bottom-left corner of the active window.

Action items:

  • Adjust the gradient effect region to make it narrower and more visually natural.
  • Make the drop shadow feature configurable via make config. If the feature is set, no bottom-right window decorator would appear.
  • Avoid changing function prototype of twin_window_create, making drop shadow independent from the configurations.
  • Ensure that drop shadow feature works with Pixman renderer instead of the built-in one.

@weihsinyeh
Copy link
Collaborator Author

This version will use the active_pix and prev_active_pix pointers that the function twin_active_pixmap() find in #88. This aims to avoid identifying the currently/previously active pixel map twice.

jserv

This comment was marked as resolved.

@weihsinyeh weihsinyeh force-pushed the drop_shadow branch 2 times, most recently from e866c9d to 6296804 Compare January 11, 2025 18:30
@weihsinyeh
Copy link
Collaborator Author

This version adds an implementation in the function twin_shadow_border() to handle the case where the vertical shadow offset is larger than the horizontal shadow offset, aiming to prevent heap buffer overflow.

include/twin.h Show resolved Hide resolved
include/twin.h Outdated Show resolved Hide resolved
include/twin_private.h Outdated Show resolved Hide resolved
@@ -286,4 +331,5 @@ void apps_multi_start(twin_screen_t *screen,
apps_ascii_start(screen, x += 20, y += 20, w, h);
apps_jelly_start(screen, x += 20, y += 20, w / 2, h);
apps_flower_start(screen, x += 20, y += 20, w, h);
apps_blur(screen, x += 20, y += 20, w / 2, h / 2);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is worthy to have an application for stack blur. Any idea to keep it?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The apps_blur() function currently only resizes tux.png. This function isn't worthwhile to show by creating a standalone window feature. If it is still not worthy to have an application by adding another function, "blur," the apps_blur() function could be removed entirely.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The drop shadow feature's availability depends on configuration settings, so it should not expose any public functions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The drop shadow functionality relies on the blur effect, but the blur effect is designed to be used independently as well. This is why in draw.c, the function twin_shadow_border() is conditionally built, while the function twin_stack_blur() is not.

include/twin.h Show resolved Hide resolved
@jserv
Copy link
Contributor

jserv commented Jan 12, 2025

xcompmgr

When using xcompmgr with the arguments -c -C -f F, I noticed it produced a common drop shadow effect rather than stack blur. I actually prefer how xcompmgr renders these drop shadows - they appear more natural and aesthetically pleasing.

@jserv
Copy link
Contributor

jserv commented Jan 12, 2025

Additionally, there will be an extra gap.

Is it feasible to eliminate the extra gap by skipping resize icon? That is,

--- a/src/window.c
+++ b/src/window.c
@@ -274,12 +274,6 @@ static void twin_window_frame(twin_window_t *window)
         twin_matrix_translate(&m, close_x, icon_y);
         twin_matrix_scale(&m, icon_size, icon_size);
         twin_icon_draw(pixmap, TwinIconClose, m);
-
-        twin_matrix_identity(&m);
-        twin_matrix_translate(&m, resize_x, resize_y);
-        twin_matrix_scale(&m, twin_int_to_fixed(TWIN_TITLE_HEIGHT),
-                          twin_int_to_fixed(TWIN_TITLE_HEIGHT));
-        twin_icon_draw(pixmap, TwinIconResize, m);
     }
 
     twin_pixmap_clip(pixmap, window->client.left, window->client.top,

include/twin.h Outdated Show resolved Hide resolved
@weihsinyeh
Copy link
Collaborator Author

This modification removes the original extra gap while retaining the original resized icon.

apps/multi.c Show resolved Hide resolved
@weihsinyeh weihsinyeh force-pushed the drop_shadow branch 2 times, most recently from 8255976 to c598a47 Compare January 13, 2025 04:51
Add the twin_stack_blur() function to implement Mario's Stack Blur
algorithm, which blurs the target pixel map. Additionally, create a blur
window to show the effect of twin_stack_blur(). Implement the
twin_drop_shadow() function to handle the pixels within the drop shadow
area of the active window's pixel map. The drop shadow effect of the
window is only visible when the window is on the top layer, ensuring the
active window stands out visually.

Implement the twin_shadow_border() function to create a darker border of
the active window that gives a more dimensional appearance.

In the twin_drop_shadow() function, twin_stack_blur() will apply a blur
effect to the pixels beneath the active window's pixel map, which aims
to create a frosted glass appearance.

Furthermore, implement the twin_cover() function to cover the target
pixels with the desired color.

Ref: https://melatonin.dev/blog/implementing-marios-stack-blur-15-times-in-cpp/
Close sysprog21#34

Signed-off-by: Wei-Hsin Yeh <[email protected]>
@jserv
Copy link
Contributor

jserv commented Jan 19, 2025

It looks much better now! However, when Pixman based rendering is used, there is no drop shadow appearing.

@weihsinyeh
Copy link
Collaborator Author

I only found that there is no drop shadow appearing when Pixman based rendering is used once. However, when I try to reproduce the issuse, it render normally.
The following image is rendered by Pixman.
Screenshot from 2025-01-20 16-30-23
The following image is rendered by built-in way.
Screenshot from 2025-01-20 16-32-07
The way to identify them is that image rendered by Pixman will have jagged black circle.

@@ -272,6 +273,50 @@ static void apps_flower_start(twin_screen_t *screen, int x, int y, int w, int h)
twin_window_show(window);
}

static void apps_blur(twin_screen_t *screen, int x, int y, int w, int h)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function should be built only if PNG support is specified.

@jserv jserv requested a review from ndsl7109256 January 20, 2025 15:33
* Add a shadowed area to the pixel map of the window to create a drop
* shadow effect.
*/
window->shadow_x = 10, window->shadow_y = 10;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe you could define 10 as a macro with a meaningful name here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Furthermore, use Kconfig to specify the shadow related configurations.

@@ -205,6 +338,11 @@ void twin_screen_update(twin_screen_t *screen)

prev_active_pix = twin_active_pixmap(screen, &active_pix);

#if defined(CONFIG_DROP_SHADOW)
/* Handle drop shadow. */
twin_drop_shadow(screen, active_pix, prev_active_pix);
Copy link
Collaborator

@ndsl7109256 ndsl7109256 Jan 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if placing twin_drop_shadow in twin_screen_update is appropriate(So is the logic that handle the color of activate window). From my understanding, twin_screen_update serves as a bridge to rendering backends, and it gets triggered whenever there are any frame updates. For example, every time a GIF frame updates, twin_screen_update is called. With this change, every GIF frame update would require checking twin_drop_shadow, which is redundant.

Maybe we could handle these window events in twin_window_dispatch. I think an additional event might be needed for deactivating a window.

Comment on lines +66 to +73
#define _twin_add_ARGB(s, d, i, t) (((t) = (s) + twin_get_8(d, i)))
#define _twin_add(s, d, t) (((t) = (s) + (d)))
#define _twin_div(d, den, i, t) \
(((t) = (d) / (den)), (t) = twin_get_8((t), 0), \
(twin_argb32_t) twin_sat(t) << (i))
#define _twin_sub_ARGB(s, d, i, t) (((t) = (s) - twin_get_8(d, i)))
#define _twin_sub(s, d, t) (((t) = (s) - (d)))
#define twin_put_8(d, i, t) (((t) = (d) << (i)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will these macros be promoted in private headers?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants