Skip to content

Commit eab1745

Browse files
committed
Render drop shadow for active window
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 <weihsinyeh168@gmail.com>
1 parent a2becfd commit eab1745

File tree

9 files changed

+453
-3
lines changed

9 files changed

+453
-3
lines changed

apps/multi.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
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
{
@@ -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);
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
}

configs/Kconfig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ config CURSOR
5555
default n
5656
depends on !BACKEND_VNC
5757

58+
config DROP_SHADOW
59+
bool "Render drop shadow for active window"
60+
default n
61+
5862
endmenu
5963

6064
menu "Image Loaders"

include/twin.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ typedef struct _twin_pixmap {
194194
* Pixels
195195
*/
196196
twin_animation_t *animation;
197+
/*
198+
* When the pixel map is within the active window, it will have a drop
199+
* shadow to enhance its visual distinction.
200+
*/
201+
bool shadow;
202+
197203
twin_pointer_t p;
198204
/*
199205
* When representing a window, this point
@@ -422,6 +428,11 @@ typedef void (*twin_destroy_func_t)(twin_window_t *window);
422428
struct _twin_window {
423429
twin_screen_t *screen;
424430
twin_pixmap_t *pixmap;
431+
432+
/* Set the shadow range for horizontal and vertical directions. */
433+
twin_coord_t shadow_offset_x;
434+
twin_coord_t shadow_offset_y;
435+
425436
twin_window_style_t style;
426437
twin_rect_t client;
427438
twin_rect_t damage;
@@ -625,6 +636,15 @@ void twin_dispatch(twin_context_t *ctx);
625636
* draw.c
626637
*/
627638

639+
void twin_stack_blur(twin_pixmap_t *px,
640+
int radius,
641+
twin_coord_t left,
642+
twin_coord_t right,
643+
twin_coord_t top,
644+
twin_coord_t bottom);
645+
646+
void twin_shadow_border(twin_pixmap_t *shadow);
647+
628648
void twin_composite(twin_pixmap_t *dst,
629649
twin_coord_t dst_x,
630650
twin_coord_t dst_y,
@@ -648,6 +668,12 @@ void twin_fill(twin_pixmap_t *dst,
648668

649669
void twin_premultiply_alpha(twin_pixmap_t *px);
650670

671+
void twin_cover(twin_pixmap_t *dst,
672+
twin_argb32_t color,
673+
twin_coord_t x,
674+
twin_coord_t y,
675+
twin_coord_t width);
676+
651677
/*
652678
* event.c
653679
*/

include/twin_private.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,21 @@ typedef int64_t twin_xfixed_t;
187187
((((s) << 5) & 0xfc00) | (((s) >> 1) & 0x300)) | \
188188
((((s) << 8) & 0xf80000) | (((s) << 3) & 0x70000)) | 0xff000000)
189189

190+
#define min(x, y) \
191+
({ \
192+
typeof(x) _x = (x); \
193+
typeof(y) _y = (y); \
194+
(void) (&_x == &_y); \
195+
_x < _y ? _x : _y; \
196+
})
197+
#define max(x, y) \
198+
({ \
199+
typeof(x) _x = (x); \
200+
typeof(y) _y = (y); \
201+
(void) (&_x == &_y); \
202+
_x > _y ? _x : _y; \
203+
})
204+
190205
typedef union {
191206
twin_pointer_t p;
192207
twin_argb32_t c;

src/draw.c

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
#include "twin_private.h"
1616

17+
#define TWIN_TITLE_HEIGHT 20
18+
1719
/* op, src, dst */
1820
static const twin_src_op comp2[2][4][3] = {
1921
[TWIN_OVER] =
@@ -302,6 +304,207 @@ static const twin_src_msk_op comp3[2][4][4][3] = {
302304
#define operand_index(o) \
303305
((o)->source_kind == TWIN_SOLID ? 3 : o->u.pixmap->format)
304306

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

src/image.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
IIF(LOADER_HAS(GIF))( \
4040
_(gif) \
4141
) \
42-
IIF(LOADER_HAS(TVG))( \
42+
IIF(LOADER_HAS(TVG))( \
4343
_(tvg) \
4444
)
4545
/* clang-format on */

0 commit comments

Comments
 (0)