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 sysprog21#34

Signed-off-by: Wei-Hsin Yeh <[email protected]>
  • Loading branch information
weihsinyeh committed Jan 7, 2025
1 parent a2becfd commit eab1745
Show file tree
Hide file tree
Showing 9 changed files with 453 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 n

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
215 changes: 215 additions & 0 deletions src/draw.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

#include "twin_private.h"

#define TWIN_TITLE_HEIGHT 20

/* op, src, dst */
static const twin_src_op comp2[2][4][3] = {
[TWIN_OVER] =
Expand Down Expand Up @@ -302,6 +304,207 @@ static const twin_src_msk_op comp3[2][4][4][3] = {
#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;
}
}
}
}

/* FIXME: source clipping is busted */
static void _twin_composite_simple(twin_pixmap_t *dst,
twin_coord_t dst_x,
Expand Down Expand Up @@ -777,3 +980,15 @@ void twin_fill(twin_pixmap_t *dst,
(*op)(twin_pixmap_pointer(dst, left, iy), src, right - left);
twin_pixmap_damage(dst, left, top, right, bottom);
}
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;
}
}
2 changes: 1 addition & 1 deletion src/image.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
IIF(LOADER_HAS(GIF))( \
_(gif) \
) \
IIF(LOADER_HAS(TVG))( \
IIF(LOADER_HAS(TVG))( \
_(tvg) \
)
/* clang-format on */
Expand Down
Loading

0 comments on commit eab1745

Please sign in to comment.