From 7e00edfce5e36e2aa7a6d60f7f69bacd1c241366 Mon Sep 17 00:00:00 2001 From: Wei-Hsin Yeh Date: Tue, 12 Nov 2024 09:49:36 +0800 Subject: [PATCH] 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/ Close #34 Signed-off-by: Wei-Hsin Yeh --- apps/multi.c | 43 ++++++++ configs/Kconfig | 4 + include/twin.h | 26 +++++ include/twin_private.h | 15 +++ src/draw-pixman.c | 219 +++++++++++++++++++++++++++++++++++++++++ src/draw.c | 216 ++++++++++++++++++++++++++++++++++++++++ src/image.c | 2 +- src/pixmap.c | 13 +++ src/screen.c | 127 +++++++++++++++++++++++- src/window.c | 12 ++- 10 files changed, 674 insertions(+), 3 deletions(-) diff --git a/apps/multi.c b/apps/multi.c index c33fd30..9f382e0 100644 --- a/apps/multi.c +++ b/apps/multi.c @@ -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) { @@ -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, @@ -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); } diff --git a/configs/Kconfig b/configs/Kconfig index 1a601fc..2562bd6 100644 --- a/configs/Kconfig +++ b/configs/Kconfig @@ -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" diff --git a/include/twin.h b/include/twin.h index e2622f4..f015026 100644 --- a/include/twin.h +++ b/include/twin.h @@ -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 @@ -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; @@ -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, @@ -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 */ diff --git a/include/twin_private.h b/include/twin_private.h index 060fde8..4546296 100644 --- a/include/twin_private.h +++ b/include/twin_private.h @@ -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; diff --git a/src/draw-pixman.c b/src/draw-pixman.c index 77cf1a2..abaa3cc 100644 --- a/src/draw-pixman.c +++ b/src/draw-pixman.c @@ -7,6 +7,8 @@ #include #include "twin_private.h" +#define TWIN_TITLE_HEIGHT 20 + static void twin_argb32_to_pixman_color(twin_argb32_t argb, pixman_color_t *color) { @@ -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, @@ -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; + } +} diff --git a/src/draw.c b/src/draw.c index e3f1655..1bef1e7 100644 --- a/src/draw.c +++ b/src/draw.c @@ -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] = @@ -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, @@ -777,3 +980,16 @@ 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; + } +} diff --git a/src/image.c b/src/image.c index 21829e0..017be4e 100644 --- a/src/image.c +++ b/src/image.c @@ -39,7 +39,7 @@ IIF(LOADER_HAS(GIF))( \ _(gif) \ ) \ - IIF(LOADER_HAS(TVG))( \ + IIF(LOADER_HAS(TVG))( \ _(tvg) \ ) /* clang-format on */ diff --git a/src/pixmap.c b/src/pixmap.c index f620411..74d0825 100644 --- a/src/pixmap.c +++ b/src/pixmap.c @@ -43,6 +43,7 @@ twin_pixmap_t *twin_pixmap_create(twin_format_t format, pixmap->stride = stride; pixmap->disable = 0; pixmap->animation = NULL; + pixmap->shadow = false; pixmap->p.v = pixmap + 1; memset(pixmap->p.v, '\0', space); return pixmap; @@ -292,8 +293,20 @@ static twin_argb32_t _twin_pixmap_fetch(twin_pixmap_t *pixmap, twin_pixmap_pointer(pixmap, x - pixmap->x, y - pixmap->y); /* FIXME: for transform */ +#if defined(CONFIG_DROP_SHADOW) + /* Handle drop shadow. */ + /* + * When the cursor fetches a pixel within the drop shadow area, it will not + * retrieve any value. + */ + if (pixmap->x <= x && + x < pixmap->x + pixmap->width - pixmap->window->shadow_offset_x && + pixmap->y <= y && + y < pixmap->y + pixmap->height - pixmap->window->shadow_offset_y) { +#else if (pixmap->x <= x && x < pixmap->x + pixmap->width && pixmap->y <= y && y < pixmap->y + pixmap->height) { +#endif switch (pixmap->format) { case TWIN_A8: return *p.a8 << 24; diff --git a/src/screen.c b/src/screen.c index 76bcfe2..955ece9 100644 --- a/src/screen.c +++ b/src/screen.c @@ -134,7 +134,7 @@ static void twin_screen_span_pixmap(twin_screen_t maybe_unused *screen, return; if (p->y + p->height <= y) return; - /* bounds check in x*/ + /* bounds check in x */ p_left = left; if (p_left < p->x) p_left = p->x; @@ -151,6 +151,125 @@ static void twin_screen_span_pixmap(twin_screen_t maybe_unused *screen, op32(dst, src, p_right - p_left); } +void twin_drop_shadow(twin_screen_t *screen) +{ + twin_pixmap_t *p = NULL, *active_pix = NULL, *prev_active_pix = NULL; + twin_pointer_t dst; + twin_source_u src; + twin_coord_t start, end, overlap, y, src_y, ori_wid, ori_hei, tgt_start, + _tgt_start, tgt_end, src_start, src_end; + + twin_src_op pop32 = _twin_argb32_over_argb32, + bop32 = _twin_argb32_source_argb32; + + /* + * Identify the previously active pixel map and the currently active pixel + * map, which is on the topmost layer. + */ + for (p = screen->bottom; p; p = p->up) { + if (p->shadow) { + /* Remove the drop shadow from the previously active pixel map. */ + src.c = 0x00000000; + for (y = 0; y < (*p).height; y++) { + if (y < (*p).height - (*p).window->shadow_offset_y) + twin_cover(p, src.c, + (*p).width - (*p).window->shadow_offset_x, y, + (*p).window->shadow_offset_x); + else + twin_cover(p, src.c, 0, y, (*p).width); + } + active_pix->shadow = false; + prev_active_pix = p; + } + active_pix = p; + } + /* Mark the previously active pixel map as damaged to update its changes. */ + if (prev_active_pix && active_pix != prev_active_pix) + twin_pixmap_damage(prev_active_pix, 0, 0, prev_active_pix->width, + prev_active_pix->height); + + /* + * The shadow effect of the window only becomes visible when the window is + * active. + */ + if (active_pix) { + active_pix->shadow = true; + ori_wid = (*active_pix).width - (*active_pix).window->shadow_offset_x; + ori_hei = (*active_pix).height - (*active_pix).window->shadow_offset_y; + tgt_start = (*active_pix).x; + tgt_end = (*active_pix).x + (*active_pix).width; + for (y = 0; y < (*active_pix).height; y++) { + /* + * Take the screen's background as the bottom layer of the shadow. + */ + if (screen->background) { + p = screen->background; + src_y = (*active_pix).y + y; + if (src_y > 0 && src_y < (*p).height - 1) { + if (y < ori_hei) + _tgt_start = tgt_start + ori_wid; + else + _tgt_start = tgt_start; + + overlap = tgt_end - _tgt_start; + src_start = _tgt_start; + + dst = twin_pixmap_pointer(active_pix, + _tgt_start - tgt_start, y); + src.p = twin_pixmap_pointer(p, src_start, src_y); + bop32(dst, src, overlap); + } + } + /* + * Render each layer of the pixmap under the shadow pixel map onto + * the shadow. + */ + for (p = screen->bottom; p; p = p->up) { + /* Only render pixel maps beneath the visible drop shadow. */ + if (p->shadow) + break; + + /* + * Identify the areas where the currently pixel map overlaps + * with the drop shadow pixel map. + */ + src_y = (*active_pix).y + y - (*p).y; + if (src_y < 0 || src_y >= (*p).height - 1) + continue; + + if (y < ori_hei) + _tgt_start = tgt_start + ori_wid; + else + _tgt_start = tgt_start; + + src_start = (*p).x; + src_end = (*p).x + (*p).width; + + if (src_start > tgt_end || _tgt_start > src_end) + continue; + start = max(src_start, _tgt_start); + end = min(src_end, tgt_end); + overlap = end - start; + dst = twin_pixmap_pointer(active_pix, start - tgt_start, y); + src.p = twin_pixmap_pointer(p, start - src_start, src_y); + pop32(dst, src, overlap); + } + } + /* + * Create a darker border of the active window that gives a more + * dimensional appearance. + */ + twin_shadow_border(active_pix); + + /* Add a frosted glass effect to the shadow of the active window. */ + /* Right side of the active window */ + twin_stack_blur(active_pix, 2, ori_wid, active_pix->width, 0, ori_hei); + /* Bottom side of the active window */ + twin_stack_blur(active_pix, 2, 0, active_pix->width, ori_hei, + active_pix->height); + } +} + void twin_screen_update(twin_screen_t *screen) { twin_coord_t left = screen->damage.left; @@ -183,6 +302,12 @@ void twin_screen_update(twin_screen_t *screen) if (screen->put_begin) (*screen->put_begin)(left, top, right, bottom, screen->closure); + +#if defined(CONFIG_DROP_SHADOW) + /* Handle drop shadow. */ + twin_drop_shadow(screen); +#endif + for (y = top; y < bottom; y++) { if (screen->background) { twin_pointer_t dst; diff --git a/src/window.c b/src/window.c index 72fa65a..bd11351 100644 --- a/src/window.c +++ b/src/window.c @@ -53,7 +53,17 @@ twin_window_t *twin_window_create(twin_screen_t *screen, window->client.top = top; window->client.right = width - right; window->client.bottom = height - bottom; - window->pixmap = twin_pixmap_create(format, width, height); + /* Add a shadow pixel map under the window with a shadow effect. */ +#if defined(CONFIG_DROP_SHADOW) + /* Handle drop shadow. */ + window->shadow_offset_x = 50, window->shadow_offset_y = 50; +#else + window->shadow_offset_x = 0, window->shadow_offset_y = 0; +#endif + window->pixmap = twin_pixmap_create(format, width + window->shadow_offset_x, + height + window->shadow_offset_y); + if (!window->pixmap) + return NULL; twin_pixmap_clip(window->pixmap, window->client.left, window->client.top, window->client.right, window->client.bottom); twin_pixmap_origin_to_clip(window->pixmap);