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