Skip to content

Commit a78fe42

Browse files
committed
Implement the drop shadow
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 a shadow pixel map beneath the window with a blur effect to create a frosted glass appearance. The drop shadow effect of the window is only visible when it is on the top layer, ensuring the active window stands out visually. Furthermore, implement the `twin_shadow_border()` function that aims to create darker edge of the active window that give a more dimensional appearance. Ref: https://melatonin.dev/blog/implementing-marios-stack-blur-15-times-in-cpp/ See #34 Signed-off-by: Wei-Hsin Yeh <[email protected]>
1 parent a2becfd commit a78fe42

File tree

7 files changed

+450
-17
lines changed

7 files changed

+450
-17
lines changed

Diff for: apps/multi.c

+49-6
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@
77
#include "apps_multi.h"
88

99
#define D(x) twin_double_to_fixed(x)
10+
#define ASSET_PATH "assets/"
1011

1112
static void apps_line_start(twin_screen_t *screen, int x, int y, int w, int h)
1213
{
1314
twin_window_t *window = twin_window_create(
14-
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h);
15+
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h, true);
1516
twin_pixmap_t *pixmap = window->pixmap;
1617
twin_path_t *stroke = twin_path_create();
1718
twin_fixed_t fy;
@@ -38,7 +39,7 @@ static void apps_circletext_start(twin_screen_t *screen,
3839
int h)
3940
{
4041
twin_window_t *window = twin_window_create(
41-
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h);
42+
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h, true);
4243
int wid = window->client.right - window->client.left;
4344
int hei = window->client.bottom - window->client.top;
4445
twin_pixmap_t *pixmap = window->pixmap;
@@ -83,7 +84,7 @@ static void apps_quickbrown_start(twin_screen_t *screen,
8384
int h)
8485
{
8586
twin_window_t *window = twin_window_create(
86-
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h);
87+
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h, true);
8788
int wid = window->client.right - window->client.left;
8889
int hei = window->client.bottom - window->client.top;
8990
twin_pixmap_t *pixmap = window->pixmap;
@@ -126,7 +127,7 @@ static void apps_quickbrown_start(twin_screen_t *screen,
126127
static void apps_ascii_start(twin_screen_t *screen, int x, int y, int w, int h)
127128
{
128129
twin_window_t *window = twin_window_create(
129-
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h);
130+
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h, true);
130131
int wid = window->client.right - window->client.left;
131132
int hei = window->client.bottom - window->client.top;
132133
twin_pixmap_t *pixmap = window->pixmap;
@@ -174,7 +175,7 @@ static void apps_ascii_start(twin_screen_t *screen, int x, int y, int w, int h)
174175
static void apps_jelly_start(twin_screen_t *screen, int x, int y, int w, int h)
175176
{
176177
twin_window_t *window = twin_window_create(
177-
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h);
178+
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h, true);
178179
int wid = window->client.right - window->client.left;
179180
int hei = window->client.bottom - window->client.top;
180181
twin_pixmap_t *pixmap = window->pixmap;
@@ -250,7 +251,7 @@ static void draw_flower(twin_path_t *path,
250251
static void apps_flower_start(twin_screen_t *screen, int x, int y, int w, int h)
251252
{
252253
twin_window_t *window = twin_window_create(
253-
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h);
254+
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h, true);
254255
twin_pixmap_t *pixmap = window->pixmap;
255256
twin_path_t *stroke = twin_path_create();
256257
twin_path_t *path = twin_path_create();
@@ -272,6 +273,47 @@ static void apps_flower_start(twin_screen_t *screen, int x, int y, int w, int h)
272273
twin_window_show(window);
273274
}
274275

276+
static void apps_test(twin_screen_t *screen, int x, int y, int w, int h)
277+
{
278+
twin_pixmap_t *raw_background =
279+
twin_pixmap_from_file(ASSET_PATH "tux.png", TWIN_ARGB32);
280+
twin_window_t *window = twin_window_create(
281+
screen, TWIN_ARGB32, TwinWindowApplication, x, y, w, h, true);
282+
twin_window_set_name(window, "Test");
283+
twin_pixmap_t *scaled_background = twin_pixmap_create(
284+
TWIN_ARGB32, window->pixmap->width, window->pixmap->height);
285+
twin_fixed_t sx, sy;
286+
sx = twin_fixed_div(
287+
twin_int_to_fixed(raw_background->width),
288+
twin_int_to_fixed(window->client.right - window->client.left));
289+
sy = twin_fixed_div(
290+
twin_int_to_fixed(raw_background->height),
291+
twin_int_to_fixed(window->client.bottom - window->client.top));
292+
293+
twin_matrix_scale(&raw_background->transform, sx, sy);
294+
twin_operand_t srcop = {
295+
.source_kind = TWIN_PIXMAP,
296+
.u.pixmap = raw_background,
297+
};
298+
299+
twin_composite(scaled_background, 0, 0, &srcop, 0, 0, 0, 0, 0, TWIN_SOURCE,
300+
screen->width, screen->height);
301+
302+
twin_pointer_t src, dst;
303+
for (int y = window->client.top; y < window->client.bottom; y++)
304+
for (int x = window->client.left; x < window->client.right; x++) {
305+
src =
306+
twin_pixmap_pointer(scaled_background, x - window->client.left,
307+
y - window->client.top);
308+
dst = twin_pixmap_pointer(window->pixmap, x, y);
309+
*dst.argb32 = *src.argb32 | 0xff000000;
310+
}
311+
312+
twin_pixmap_destroy(scaled_background);
313+
twin_pixmap_destroy(raw_background);
314+
twin_window_show(window);
315+
}
316+
275317
void apps_multi_start(twin_screen_t *screen,
276318
const char *name,
277319
int x,
@@ -286,4 +328,5 @@ void apps_multi_start(twin_screen_t *screen,
286328
apps_ascii_start(screen, x += 20, y += 20, w, h);
287329
apps_jelly_start(screen, x += 20, y += 20, w / 2, h);
288330
apps_flower_start(screen, x += 20, y += 20, w, h);
331+
apps_test(screen, x += 20, y += 20, w, h);
289332
}

Diff for: include/twin.h

+27-1
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,18 @@ typedef struct _twin_pixmap {
194194
* Pixels
195195
*/
196196
twin_animation_t *animation;
197+
/* Drop Shadow variables */
198+
/*
199+
* When the pixel map is set to shadow, any mouse events triggered on the
200+
* pixel map will not result in any event being triggered.
201+
*/
202+
bool shadow;
203+
/*
204+
* When the shadow pixel map belongs to the active window, use a drop shadow
205+
* to make it visually distinct.
206+
*/
207+
bool visible;
208+
197209
twin_pointer_t p;
198210
/*
199211
* When representing a window, this point
@@ -422,6 +434,10 @@ typedef void (*twin_destroy_func_t)(twin_window_t *window);
422434
struct _twin_window {
423435
twin_screen_t *screen;
424436
twin_pixmap_t *pixmap;
437+
bool shadow;
438+
twin_pixmap_t *shadow_pixmap;
439+
twin_coord_t shadow_offset_x;
440+
twin_coord_t shadow_offset_y;
425441
twin_window_style_t style;
426442
twin_rect_t client;
427443
twin_rect_t damage;
@@ -625,6 +641,15 @@ void twin_dispatch(twin_context_t *ctx);
625641
* draw.c
626642
*/
627643

644+
void twin_stack_blur(twin_pixmap_t *px,
645+
int radius,
646+
twin_coord_t left,
647+
twin_coord_t right,
648+
twin_coord_t top,
649+
twin_coord_t bottom);
650+
651+
void twin_shadow_border(twin_pixmap_t *shadow);
652+
628653
void twin_composite(twin_pixmap_t *dst,
629654
twin_coord_t dst_x,
630655
twin_coord_t dst_y,
@@ -1142,7 +1167,8 @@ twin_window_t *twin_window_create(twin_screen_t *screen,
11421167
twin_coord_t x,
11431168
twin_coord_t y,
11441169
twin_coord_t width,
1145-
twin_coord_t height);
1170+
twin_coord_t height,
1171+
bool shadow);
11461172

11471173
void twin_window_destroy(twin_window_t *window);
11481174

Diff for: src/draw.c

+204
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,210 @@ static const twin_src_msk_op comp3[2][4][4][3] = {
302302
#define operand_index(o) \
303303
((o)->source_kind == TWIN_SOLID ? 3 : o->u.pixmap->format)
304304

305+
#define _twin_add_ARGB(s, d, i, t) (((t) = (s) + twin_get_8(d, i)))
306+
#define _twin_add(s, d, t) (((t) = (s) + (d)))
307+
#define _twin_div(d, den, i, t) \
308+
(((t) = (d) / (den)), (t) = twin_get_8((t), 0), \
309+
(twin_argb32_t) twin_sat(t) << (i))
310+
#define _twin_sub_ARGB(s, d, i, t) (((t) = (s) - twin_get_8(d, i)))
311+
#define _twin_sub(s, d, t) (((t) = (s) - (d)))
312+
#define twin_put_8(d, i, t) (((t) = (d) << (i)))
313+
314+
#define min(x, y) \
315+
({ \
316+
typeof(x) _x = (x); \
317+
typeof(y) _y = (y); \
318+
(void) (&_x == &_y); \
319+
_x < _y ? _x : _y; \
320+
})
321+
#define max(x, y) \
322+
({ \
323+
typeof(x) _x = (x); \
324+
typeof(y) _y = (y); \
325+
(void) (&_x == &_y); \
326+
_x > _y ? _x : _y; \
327+
})
328+
329+
void twin_stack(twin_pixmap_t *trg_px,
330+
twin_pixmap_t *src_px,
331+
int radius,
332+
int first_str,
333+
int first_end,
334+
int second_str,
335+
int second_end,
336+
bool horiz_scan)
337+
{
338+
int den = (radius + 1) * (radius + 1);
339+
twin_pointer_t src_ptr, trg_ptr, old_ptr, new_ptr;
340+
twin_argb32_t sumInR, sumOutR, sumR, sumInG, sumOutG, sumG, sumInB, sumOutB,
341+
sumB, _cur, _old, _new, _src;
342+
uint16_t t1, t2, t3;
343+
for (int first = first_str; first < first_end; first++) {
344+
sumInR = sumOutR = sumR = sumInG = sumOutG = sumG = sumInB = sumOutB =
345+
sumB = 0x00000000;
346+
347+
/* Initialize SumOut by padding */
348+
if (horiz_scan)
349+
src_ptr = twin_pixmap_pointer(src_px, second_str, first);
350+
else
351+
src_ptr = twin_pixmap_pointer(src_px, first, second_str);
352+
_src = *src_ptr.argb32;
353+
354+
for (int i = second_str; i < second_str + radius; i++) {
355+
sumOutR = _twin_add_ARGB(sumOutR, _src, 0, t1);
356+
sumOutG = _twin_add_ARGB(sumOutG, _src, 8, t2);
357+
sumOutB = _twin_add_ARGB(sumOutB, _src, 16, t3);
358+
for (int j = 0; j < (i - second_str) + 1; j++) {
359+
sumR = _twin_add_ARGB(sumR, _src, 0, t1);
360+
sumG = _twin_add_ARGB(sumG, _src, 8, t2);
361+
sumB = _twin_add_ARGB(sumB, _src, 16, t3);
362+
}
363+
}
364+
365+
/* Initialize SumIn */
366+
for (int i = second_str; i < second_str + radius; i++) {
367+
if (horiz_scan)
368+
src_ptr = twin_pixmap_pointer(src_px, i, first);
369+
else
370+
src_ptr = twin_pixmap_pointer(src_px, first, i);
371+
_src = *src_ptr.argb32;
372+
sumInR = _twin_add_ARGB(sumInR, _src, 0, t1);
373+
sumInG = _twin_add_ARGB(sumInG, _src, 8, t2);
374+
sumInB = _twin_add_ARGB(sumInB, _src, 16, t3);
375+
for (int j = 0; j < radius - (i - second_str); j++) {
376+
sumR = _twin_add_ARGB(sumR, _src, 0, t1);
377+
sumG = _twin_add_ARGB(sumG, _src, 8, t2);
378+
sumB = _twin_add_ARGB(sumB, _src, 16, t3);
379+
}
380+
}
381+
382+
for (int cur = second_str; cur < second_end; cur++) {
383+
if (horiz_scan) {
384+
src_ptr = twin_pixmap_pointer(src_px, cur, first);
385+
trg_ptr = twin_pixmap_pointer(trg_px, cur, first);
386+
old_ptr = twin_pixmap_pointer(
387+
src_px, max(cur - radius, second_str), first);
388+
new_ptr = twin_pixmap_pointer(
389+
src_px, min(cur + radius, second_end - 1), first);
390+
} else {
391+
src_ptr = twin_pixmap_pointer(src_px, first, cur);
392+
trg_ptr = twin_pixmap_pointer(trg_px, first, cur);
393+
old_ptr = twin_pixmap_pointer(src_px, first,
394+
max(cur - radius, second_str));
395+
new_ptr = twin_pixmap_pointer(
396+
src_px, first, min(cur + radius, second_end - 1));
397+
}
398+
_cur = *src_ptr.argb32;
399+
_old = *old_ptr.argb32;
400+
_new = *new_ptr.argb32;
401+
/* STEP 1 : sum_out + current */
402+
sumOutR = _twin_add_ARGB(sumOutR, _cur, 0, t1);
403+
sumOutG = _twin_add_ARGB(sumOutG, _cur, 8, t2);
404+
sumOutB = _twin_add_ARGB(sumOutB, _cur, 16, t3);
405+
/* STEP 2 : sum_in + new */
406+
sumInR = _twin_add_ARGB(sumInR, _new, 0, t1);
407+
sumInG = _twin_add_ARGB(sumInG, _new, 8, t2);
408+
sumInB = _twin_add_ARGB(sumInB, _new, 16, t3);
409+
/* STEP 3 : sum + sum_in */
410+
sumR = _twin_add(sumR, sumInR, t1);
411+
sumG = _twin_add(sumG, sumInG, t2);
412+
sumB = _twin_add(sumB, sumInB, t3);
413+
/* STEP 4 : sum / denominator */
414+
*trg_ptr.argb32 =
415+
(_twin_div(sumR, den, 0, t1) | _twin_div(sumG, den, 8, t2) |
416+
_twin_div(sumB, den, 16, t3) | (*src_ptr.argb32 & 0xff000000));
417+
/* STEP 5 : sum - sum_out */
418+
sumR = _twin_sub(sumR, sumOutR, t1);
419+
sumG = _twin_sub(sumG, sumOutG, t2);
420+
sumB = _twin_sub(sumB, sumOutB, t3);
421+
/* STEP 6 : sum_out - old */
422+
sumOutR = _twin_sub_ARGB(sumOutR, _old, 0, t1);
423+
sumOutG = _twin_sub_ARGB(sumOutG, _old, 8, t2);
424+
sumOutB = _twin_sub_ARGB(sumOutB, _old, 16, t3);
425+
/* STEP 7 : sum_in - current */
426+
sumInR = _twin_sub_ARGB(sumInR, _cur, 0, t1);
427+
sumInG = _twin_sub_ARGB(sumInG, _cur, 8, t2);
428+
sumInB = _twin_sub_ARGB(sumInB, _cur, 16, t3);
429+
}
430+
}
431+
}
432+
433+
void twin_stack_blur(twin_pixmap_t *px,
434+
int radius,
435+
twin_coord_t left,
436+
twin_coord_t right,
437+
twin_coord_t top,
438+
twin_coord_t bottom)
439+
{
440+
if (px->format != TWIN_ARGB32)
441+
return;
442+
twin_pixmap_t *tmp_px =
443+
twin_pixmap_create(px->format, px->width, px->height);
444+
memcpy(tmp_px->p.v, px->p.v,
445+
px->width * px->height * twin_bytes_per_pixel(px->format));
446+
/*
447+
* Originally, performing a 2D convolution on each pixel takes O(width *
448+
* height * k²). However, by first scanning horizontally and then vertically
449+
* across the pixel map, and applying a 1D convolution to each pixel, the
450+
* complexity is reduced to O(2 * width * height * k).
451+
*/
452+
twin_stack(tmp_px, px, radius, top, bottom, left, right, true);
453+
twin_stack(px, tmp_px, radius, left, right, top, bottom, false);
454+
twin_pixmap_destroy(tmp_px);
455+
return;
456+
}
457+
458+
void twin_shadow_border(twin_pixmap_t *shadow)
459+
{
460+
twin_pixmap_t *target = shadow->window->pixmap;
461+
twin_coord_t target_right = target->x + target->width,
462+
target_bottom = target->y + target->height, border = 5,
463+
right_edge = (*shadow).width - ((*shadow).x - (*target).x),
464+
bottom_edge = (*shadow).height - ((*shadow).y - (*target).y),
465+
right_span = right_edge;
466+
twin_pointer_t dst;
467+
twin_source_u src;
468+
twin_argb32_t color_x, color_y = 0xff000000;
469+
for (twin_coord_t y = 0; y < (*shadow).height; y++) {
470+
color_x = 0xff000000;
471+
/* Render the right edge of the shadow border. */
472+
if (shadow->y + y < target_bottom) {
473+
for (twin_coord_t x = right_edge - border; x < (*shadow).width; x++)
474+
if (shadow->x + x > target_right - border) {
475+
dst = twin_pixmap_pointer(shadow, x, y);
476+
src.c = color_x;
477+
_twin_c_over_argb32(dst, src, 1);
478+
if ((shadow->x + x - target_right) % border == 0) {
479+
color_x >>= 1;
480+
color_x &= 0xff000000;
481+
}
482+
}
483+
} else
484+
/* Calculate the range of the corner. */
485+
right_span++;
486+
487+
/* Render the bottom edge of the shadow border. */
488+
if (shadow->y + y > target_bottom - border) {
489+
dst = twin_pixmap_pointer(shadow, 0, y);
490+
src.c = color_y;
491+
_twin_c_over_argb32(dst, src, right_edge);
492+
/* Render the bottom-right corner of the shadow border. */
493+
for (twin_coord_t i = 0; i < right_span - right_edge; i++) {
494+
/* The corner's pixels are symmetrical to the diagonal. */
495+
dst = twin_pixmap_pointer(shadow, right_edge + i, y);
496+
_twin_c_over_argb32(dst, src, 1);
497+
dst = twin_pixmap_pointer(
498+
shadow, right_edge + (y - bottom_edge), bottom_edge + i);
499+
_twin_c_over_argb32(dst, src, 1);
500+
}
501+
if ((shadow->y + y - target_bottom) % border == 0) {
502+
color_y >>= 1;
503+
color_y &= 0xff000000;
504+
}
505+
}
506+
}
507+
}
508+
305509
/* FIXME: source clipping is busted */
306510
static void _twin_composite_simple(twin_pixmap_t *dst,
307511
twin_coord_t dst_x,

Diff for: src/pixmap.c

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ twin_pixmap_t *twin_pixmap_create(twin_format_t format,
4343
pixmap->stride = stride;
4444
pixmap->disable = 0;
4545
pixmap->animation = NULL;
46+
pixmap->shadow = false;
47+
pixmap->visible = false;
4648
pixmap->p.v = pixmap + 1;
4749
memset(pixmap->p.v, '\0', space);
4850
return pixmap;

0 commit comments

Comments
 (0)