Skip to content

Commit

Permalink
Render drop shadow for active window
Browse files Browse the repository at this point in the history
Add the `twin_stack_blur()` function to implement Mario's Stack Blur
algorithm, which blurs the target pixel map. Additionally, create a test
window to evaluate 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/
See #34

Signed-off-by: Wei-Hsin Yeh <weihsinyeh168@gmail.com>
  • Loading branch information
weihsinyeh committed Jan 9, 2025
1 parent a2becfd commit 1be5493
Show file tree
Hide file tree
Showing 10 changed files with 674 additions and 3 deletions.
43 changes: 43 additions & 0 deletions apps/multi.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "apps_multi.h"

#define D(x) twin_double_to_fixed(x)
#define ASSET_PATH "assets/"

static void apps_line_start(twin_screen_t *screen, int x, int y, int w, int h)
{
Expand Down Expand Up @@ -272,6 +273,47 @@ static void apps_flower_start(twin_screen_t *screen, int x, int y, int w, int h)
twin_window_show(window);
}

static void apps_test(twin_screen_t *screen, int x, int y, int w, int h)
{
twin_pixmap_t *raw_background =
twin_pixmap_from_file(ASSET_PATH "tux.png", TWIN_ARGB32);
twin_window_t *window = twin_window_create(
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h);
twin_window_set_name(window, "Test");
twin_pixmap_t *scaled_background = twin_pixmap_create(
TWIN_ARGB32, window->pixmap->width, window->pixmap->height);
twin_fixed_t sx, sy;
sx = twin_fixed_div(
twin_int_to_fixed(raw_background->width),
twin_int_to_fixed(window->client.right - window->client.left));
sy = twin_fixed_div(
twin_int_to_fixed(raw_background->height),
twin_int_to_fixed(window->client.bottom - window->client.top));

twin_matrix_scale(&raw_background->transform, sx, sy);
twin_operand_t srcop = {
.source_kind = TWIN_PIXMAP,
.u.pixmap = raw_background,
};

twin_composite(scaled_background, 0, 0, &srcop, 0, 0, 0, 0, 0, TWIN_SOURCE,
screen->width, screen->height);

twin_pointer_t src, dst;
for (int y = window->client.top; y < window->client.bottom; y++)
for (int x = window->client.left; x < window->client.right; x++) {
src =
twin_pixmap_pointer(scaled_background, x - window->client.left,
y - window->client.top);
dst = twin_pixmap_pointer(window->pixmap, x, y);
*dst.argb32 = *src.argb32 | 0xff000000;
}

twin_pixmap_destroy(scaled_background);
twin_pixmap_destroy(raw_background);
twin_window_show(window);
}

void apps_multi_start(twin_screen_t *screen,
const char *name,
int x,
Expand All @@ -286,4 +328,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_test(screen, x += 20, y += 20, w, h);
}
4 changes: 4 additions & 0 deletions configs/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ config CURSOR
default n
depends on !BACKEND_VNC

config DROP_SHADOW
bool "Render drop shadow for active window"
default y

endmenu

menu "Image Loaders"
Expand Down
26 changes: 26 additions & 0 deletions include/twin.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,12 @@ typedef struct _twin_pixmap {
* Pixels
*/
twin_animation_t *animation;
/*
* When the pixel map is within the active window, it will have a drop
* shadow to enhance its visual distinction.
*/
bool shadow;

twin_pointer_t p;
/*
* When representing a window, this point
Expand Down Expand Up @@ -422,6 +428,11 @@ typedef void (*twin_destroy_func_t)(twin_window_t *window);
struct _twin_window {
twin_screen_t *screen;
twin_pixmap_t *pixmap;

/* Set the shadow range for horizontal and vertical directions. */
twin_coord_t shadow_offset_x;
twin_coord_t shadow_offset_y;

twin_window_style_t style;
twin_rect_t client;
twin_rect_t damage;
Expand Down Expand Up @@ -625,6 +636,15 @@ void twin_dispatch(twin_context_t *ctx);
* draw.c
*/

void twin_stack_blur(twin_pixmap_t *px,
int radius,
twin_coord_t left,
twin_coord_t right,
twin_coord_t top,
twin_coord_t bottom);

void twin_shadow_border(twin_pixmap_t *shadow);

void twin_composite(twin_pixmap_t *dst,
twin_coord_t dst_x,
twin_coord_t dst_y,
Expand All @@ -648,6 +668,12 @@ void twin_fill(twin_pixmap_t *dst,

void twin_premultiply_alpha(twin_pixmap_t *px);

void twin_cover(twin_pixmap_t *dst,
twin_argb32_t color,
twin_coord_t x,
twin_coord_t y,
twin_coord_t width);

/*
* event.c
*/
Expand Down
15 changes: 15 additions & 0 deletions include/twin_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,21 @@ typedef int64_t twin_xfixed_t;
((((s) << 5) & 0xfc00) | (((s) >> 1) & 0x300)) | \
((((s) << 8) & 0xf80000) | (((s) << 3) & 0x70000)) | 0xff000000)

#define min(x, y) \
({ \
typeof(x) _x = (x); \
typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x < _y ? _x : _y; \
})
#define max(x, y) \
({ \
typeof(x) _x = (x); \
typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x > _y ? _x : _y; \
})

typedef union {
twin_pointer_t p;
twin_argb32_t c;
Expand Down
219 changes: 219 additions & 0 deletions src/draw-pixman.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
#include <pixman.h>
#include "twin_private.h"

#define TWIN_TITLE_HEIGHT 20

static void twin_argb32_to_pixman_color(twin_argb32_t argb,
pixman_color_t *color)
{
Expand Down Expand Up @@ -61,6 +63,210 @@ static void pixmap_matrix_scale(pixman_image_t *src, twin_matrix_t *matrix)
pixman_image_set_transform(src, &transform);
}

#define operand_index(o) \
((o)->source_kind == TWIN_SOLID ? 3 : o->u.pixmap->format)

#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)))

void twin_stack(twin_pixmap_t *trg_px,
twin_pixmap_t *src_px,
int radius,
int first_str,
int first_end,
int second_str,
int second_end,
bool horiz_scan)
{
int den = (radius + 1) * (radius + 1);
twin_pointer_t src_ptr, trg_ptr, old_ptr, new_ptr;
twin_argb32_t sumInR, sumOutR, sumR, sumInG, sumOutG, sumG, sumInB, sumOutB,
sumB, _cur, _old, _new, _src;
uint16_t t1, t2, t3;
for (int first = first_str; first < first_end; first++) {
sumInR = sumOutR = sumR = sumInG = sumOutG = sumG = sumInB = sumOutB =
sumB = 0x00000000;

/* Initialize SumOut by padding */
if (horiz_scan)
src_ptr = twin_pixmap_pointer(src_px, second_str, first);
else
src_ptr = twin_pixmap_pointer(src_px, first, second_str);
_src = *src_ptr.argb32;

for (int i = second_str; i < second_str + radius; i++) {
sumOutR = _twin_add_ARGB(sumOutR, _src, 0, t1);
sumOutG = _twin_add_ARGB(sumOutG, _src, 8, t2);
sumOutB = _twin_add_ARGB(sumOutB, _src, 16, t3);
for (int j = 0; j < (i - second_str) + 1; j++) {
sumR = _twin_add_ARGB(sumR, _src, 0, t1);
sumG = _twin_add_ARGB(sumG, _src, 8, t2);
sumB = _twin_add_ARGB(sumB, _src, 16, t3);
}
}

/* Initialize SumIn */
for (int i = second_str; i < second_str + radius; i++) {
if (horiz_scan)
src_ptr = twin_pixmap_pointer(src_px, i, first);
else
src_ptr = twin_pixmap_pointer(src_px, first, i);
_src = *src_ptr.argb32;
sumInR = _twin_add_ARGB(sumInR, _src, 0, t1);
sumInG = _twin_add_ARGB(sumInG, _src, 8, t2);
sumInB = _twin_add_ARGB(sumInB, _src, 16, t3);
for (int j = 0; j < radius - (i - second_str); j++) {
sumR = _twin_add_ARGB(sumR, _src, 0, t1);
sumG = _twin_add_ARGB(sumG, _src, 8, t2);
sumB = _twin_add_ARGB(sumB, _src, 16, t3);
}
}

for (int cur = second_str; cur < second_end; cur++) {
if (horiz_scan) {
src_ptr = twin_pixmap_pointer(src_px, cur, first);
trg_ptr = twin_pixmap_pointer(trg_px, cur, first);
old_ptr = twin_pixmap_pointer(
src_px, max(cur - radius, second_str), first);
new_ptr = twin_pixmap_pointer(
src_px, min(cur + radius, second_end - 1), first);
} else {
src_ptr = twin_pixmap_pointer(src_px, first, cur);
trg_ptr = twin_pixmap_pointer(trg_px, first, cur);
old_ptr = twin_pixmap_pointer(src_px, first,
max(cur - radius, second_str));
new_ptr = twin_pixmap_pointer(
src_px, first, min(cur + radius, second_end - 1));
}
_cur = *src_ptr.argb32;
_old = *old_ptr.argb32;
_new = *new_ptr.argb32;
/* STEP 1 : sum_out + current */
sumOutR = _twin_add_ARGB(sumOutR, _cur, 0, t1);
sumOutG = _twin_add_ARGB(sumOutG, _cur, 8, t2);
sumOutB = _twin_add_ARGB(sumOutB, _cur, 16, t3);
/* STEP 2 : sum_in + new */
sumInR = _twin_add_ARGB(sumInR, _new, 0, t1);
sumInG = _twin_add_ARGB(sumInG, _new, 8, t2);
sumInB = _twin_add_ARGB(sumInB, _new, 16, t3);
/* STEP 3 : sum + sum_in */
sumR = _twin_add(sumR, sumInR, t1);
sumG = _twin_add(sumG, sumInG, t2);
sumB = _twin_add(sumB, sumInB, t3);
/* STEP 4 : sum / denominator */
*trg_ptr.argb32 =
(_twin_div(sumR, den, 0, t1) | _twin_div(sumG, den, 8, t2) |
_twin_div(sumB, den, 16, t3) | (*src_ptr.argb32 & 0xff000000));
/* STEP 5 : sum - sum_out */
sumR = _twin_sub(sumR, sumOutR, t1);
sumG = _twin_sub(sumG, sumOutG, t2);
sumB = _twin_sub(sumB, sumOutB, t3);
/* STEP 6 : sum_out - old */
sumOutR = _twin_sub_ARGB(sumOutR, _old, 0, t1);
sumOutG = _twin_sub_ARGB(sumOutG, _old, 8, t2);
sumOutB = _twin_sub_ARGB(sumOutB, _old, 16, t3);
/* STEP 7 : sum_in - current */
sumInR = _twin_sub_ARGB(sumInR, _cur, 0, t1);
sumInG = _twin_sub_ARGB(sumInG, _cur, 8, t2);
sumInB = _twin_sub_ARGB(sumInB, _cur, 16, t3);
}
}
}

void twin_stack_blur(twin_pixmap_t *px,
int radius,
twin_coord_t left,
twin_coord_t right,
twin_coord_t top,
twin_coord_t bottom)
{
if (px->format != TWIN_ARGB32)
return;
twin_pixmap_t *tmp_px =
twin_pixmap_create(px->format, px->width, px->height);
memcpy(tmp_px->p.v, px->p.v,
px->width * px->height * twin_bytes_per_pixel(px->format));
/*
* Originally, performing a 2D convolution on each pixel takes O(width *
* height * k²). However, by first scanning horizontally and then vertically
* across the pixel map, and applying a 1D convolution to each pixel, the
* complexity is reduced to O(2 * width * height * k).
*/
twin_stack(tmp_px, px, radius, top, bottom, left, right, true);
twin_stack(px, tmp_px, radius, left, right, top, bottom, false);
twin_pixmap_destroy(tmp_px);
return;
}

void twin_shadow_border(twin_pixmap_t *shadow)
{
twin_coord_t right_edge =
(*shadow).width - (*shadow).window->shadow_offset_x,
bottom_edge =
(*shadow).height - (*shadow).window->shadow_offset_y,
right_span = right_edge, clip, stride = 2;
twin_pointer_t dst;
twin_source_u src;
twin_argb32_t color_x, color_y = 0xff000000, color_shift = 0x0e000000;
for (twin_coord_t y = TWIN_TITLE_HEIGHT; y < (*shadow).height; y++) {
color_x = 0xff000000;
/* Render the right edge of the shadow border. */
if (y < bottom_edge) {
/*
* Create a shadow with a diagonal shape, extending from the
* top-left to the bottom-right.
*/
clip = max(
0, shadow->window->shadow_offset_x - (y - TWIN_TITLE_HEIGHT));
for (twin_coord_t x = right_edge; x < (*shadow).width - clip; x++) {
dst = twin_pixmap_pointer(shadow, x, y);
src.c = color_x;
_twin_c_over_argb32(dst, src, 1);
if ((x - right_edge) % stride == 0) {
if (color_x > color_shift)
color_x -= color_shift;
color_x &= 0xff000000;
}
}
} else
/* Calculate the range of the corner. */
right_span++;

/* Render the bottom edge of the shadow border. */
if (y > bottom_edge) {
/*
* Create a shadow with a diagonal shape, extending from the
* top-left to the bottom-right.
*/
clip = max(0, y - bottom_edge);
dst = twin_pixmap_pointer(shadow, clip, y);
src.c = color_y;
_twin_c_over_argb32(dst, src, right_edge - clip);

/* Render the bottom-right corner of the shadow border. */
for (twin_coord_t i = 0; i < right_span - right_edge; i++) {
/* The corner's pixels are symmetrical to the diagonal. */
dst = twin_pixmap_pointer(shadow, right_edge + i, y);
_twin_c_over_argb32(dst, src, 1);
dst = twin_pixmap_pointer(
shadow, right_edge + (y - bottom_edge), bottom_edge + i);
_twin_c_over_argb32(dst, src, 1);
}
if ((shadow->y + y - bottom_edge) % stride == 0) {
if (color_y > color_shift)
color_y -= color_shift;
color_y &= 0xff000000;
}
}
}
}

void twin_composite(twin_pixmap_t *_dst,
twin_coord_t dst_x,
twin_coord_t dst_y,
Expand Down Expand Up @@ -190,3 +396,16 @@ void twin_premultiply_alpha(twin_pixmap_t *px)
p.argb32[x] = _twin_apply_alpha(p.argb32[x]);
}
}

void twin_cover(twin_pixmap_t *dst,
twin_argb32_t color,
twin_coord_t x,
twin_coord_t y,
twin_coord_t width)
{
twin_pointer_t pt;
for (twin_coord_t i = 0; i < width; i++) {
pt = twin_pixmap_pointer(dst, x + i, y);
*pt.argb32 = color;
}
}
Loading

0 comments on commit 1be5493

Please sign in to comment.