From 3cab35197c6700cc6309ca804829c27484847cc5 Mon Sep 17 00:00:00 2001 From: Jim Huang Date: Sun, 26 Oct 2025 23:10:42 +0800 Subject: [PATCH] Adopt CSS-style drop shadow mask This commit generates shadow_gaussian lookup table during the build, so the renderer stops depending on runtime math. It replaces the stack-blur border writes with a lookup-table renderer that matches CSS behavior and stays efficient. For performance considerations, it caches the vertical and bottom falloff tables and rely on memset() or pointer writes to trim per-frame work. It exposes a small configurable fade tail and update defaults plus the API, so it aligns with CSS box-shadow semantics. --- .gitignore | 1 + Makefile | 7 + configs/Kconfig | 19 +- scripts/gen-shadow-lut.py | 64 +++++++ src/draw-common.c | 379 +++++++++++++++++++++++++++++++++----- src/twin_private.h | 5 +- src/window.c | 18 +- 7 files changed, 432 insertions(+), 61 deletions(-) create mode 100755 scripts/gen-shadow-lut.py diff --git a/.gitignore b/.gitignore index 3d4aa216..0e9faf3c 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ mado-perf font-edit .font-edit src/composite-decls.h +src/shadow-gaussian-lut.h # Swap [._]*.s[a-v][a-z] diff --git a/Makefile b/Makefile index 1175ae27..91d78aa7 100644 --- a/Makefile +++ b/Makefile @@ -83,6 +83,10 @@ src/composite-decls.h: scripts/gen-composite-decls.py @echo " GEN $@" @$< > $@ +src/shadow-gaussian-lut.h: scripts/gen-shadow-lut.py + @echo " GEN $@" + @./scripts/gen-shadow-lut.py $@ + # Optional features libtwin.a_files-$(CONFIG_LOGGING) += src/log.c @@ -277,6 +281,9 @@ ifeq ($(filter config defconfig clean,$(MAKECMDGOALS)),) ifeq ($(wildcard src/composite-decls.h),) $(shell scripts/gen-composite-decls.py > src/composite-decls.h) endif + ifeq ($(wildcard src/shadow-gaussian-lut.h),) + $(shell ./scripts/gen-shadow-lut.py src/shadow-gaussian-lut.h) + endif endif # Only skip build rules when running ONLY config/defconfig (no other targets) diff --git a/configs/Kconfig b/configs/Kconfig index a50c58af..d18827b2 100644 --- a/configs/Kconfig +++ b/configs/Kconfig @@ -191,32 +191,45 @@ config DROP_SHADOW config HORIZONTAL_OFFSET int "Horizontal offset" - default 1 + default 6 range 1 10 depends on DROP_SHADOW help Horizontal shadow offset in pixels. + Default matches a CSS-style soft shadow (≈6 px offset). Larger values push shadow further right. config VERTICAL_OFFSET int "Vertical offset" - default 1 + default 6 range 1 10 depends on DROP_SHADOW help Vertical shadow offset in pixels. + Default matches a CSS-style soft shadow (≈6 px offset). Larger values push shadow further down. config SHADOW_BLUR int "Shadow blur radius" - default 10 + default 12 range 1 15 depends on DROP_SHADOW help Shadow blur kernel size (radius). + Default 12 approximates common CSS box-shadow softness. Larger values create softer, more diffuse shadows. Higher values increase rendering cost. +config SHADOW_FADE_TAIL + int "Shadow fade tail" + default 2 + range 0 3 + depends on DROP_SHADOW + help + Additional zero-alpha pixels appended to the shadow mask. + Default adds ~2 px of clear space to match CSS feathering. + Higher values slightly enlarge the off-screen buffer usage. + endmenu menu "Image Loaders" diff --git a/scripts/gen-shadow-lut.py b/scripts/gen-shadow-lut.py new file mode 100755 index 00000000..69d6fe4b --- /dev/null +++ b/scripts/gen-shadow-lut.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +""" +Generate precomputed Gaussian-style weights for the drop shadow LUT. + +The curve approximates (1 - t^2)^2 * 0.92 + 0.08 sampled at 17 points +between 0 and 1 (inclusive). Values are emitted in 16.16 fixed-point so +the renderer can remain purely fixed-point. +""" + +import math +import pathlib +import sys + + +def compute_weights(samples: int = 16) -> list[int]: + weights = [] + for idx in range(samples + 1): + t = idx / samples + value = ((1.0 - t * t) ** 2) * 0.92 + 0.08 + fixed = int(round(value * 65536)) # 16.16 fixed-point + fixed = max(0, min(fixed, 0xFFFFFFFF)) + weights.append(fixed) + return weights + + +HEADER_TEMPLATE = """\ +/* + * This file is auto-generated by scripts/gen-shadow-lut.py. + * Do not edit manually. + */ +#ifndef SHADOW_GAUSSIAN_LUT_H +#define SHADOW_GAUSSIAN_LUT_H + +#include "twin.h" + +static const twin_fixed_t shadow_gaussian_lut[{count}] = {{ +{body} +}}; + +#endif /* SHADOW_GAUSSIAN_LUT_H */ +""" + + +def emit_header(path: pathlib.Path) -> None: + weights = compute_weights() + lines = [] + for idx, value in enumerate(weights): + suffix = "," if idx < len(weights) - 1 else "" + lines.append(f" 0x{value:05x}{suffix}") + header = HEADER_TEMPLATE.format(count=len(weights), body="\n".join(lines)) + path.write_text(header, encoding="utf-8") + + +def main(argv: list[str]) -> int: + if len(argv) != 2: + sys.stderr.write("Usage: gen-shadow-lut.py \n") + return 1 + output_path = pathlib.Path(argv[1]) + emit_header(output_path) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) diff --git a/src/draw-common.c b/src/draw-common.c index 859eeeaa..7cc82958 100644 --- a/src/draw-common.c +++ b/src/draw-common.c @@ -1,10 +1,13 @@ /* * Twin - A Tiny Window System * Copyright (c) 2004 Keith Packard - * Copyright (c) 2024 National Cheng Kung University, Taiwan + * Copyright (c) 2024-2025 National Cheng Kung University, Taiwan * All rights reserved. */ +#include + +#include "shadow-gaussian-lut.h" #include "twin_private.h" #define TWIN_TITLE_HEIGHT 20 @@ -152,23 +155,75 @@ void twin_stack_blur(twin_pixmap_t *px, } #if defined(CONFIG_DROP_SHADOW) -void twin_shadow_border(twin_pixmap_t *shadow, - twin_argb32_t color, - twin_coord_t offset_x, - twin_coord_t offset_y) +/* Reuse bottom-strip weights between frames when width is unchanged. */ +static twin_a8_t *shadow_bottom_cache_weights; +static twin_coord_t shadow_bottom_cache_capacity; +static twin_coord_t shadow_bottom_cache_width; + +/* Remember last vertical alpha ramp keyed by offset+alpha. */ +static twin_a8_t shadow_alpha_y_cache[(CONFIG_VERTICAL_OFFSET > 0) + ? CONFIG_VERTICAL_OFFSET + : 1]; +static twin_coord_t shadow_alpha_y_cache_offset = -1; +static twin_a8_t shadow_alpha_y_cache_alpha; +static bool shadow_alpha_y_cache_valid; + +#define SHADOW_LUT_X_LEN (CONFIG_HORIZONTAL_OFFSET + CONFIG_SHADOW_FADE_TAIL) +#define SHADOW_LUT_Y_LEN (CONFIG_VERTICAL_OFFSET + CONFIG_SHADOW_FADE_TAIL) + +/* Fast lookup: 17-entry approximation of (1 - t^2)^2 * 0.92 + 0.08. */ +static inline twin_fixed_t shadow_gaussian_weight(twin_fixed_t t) +{ + if (t <= 0) + return shadow_gaussian_lut[0]; + if (t >= TWIN_FIXED_ONE) + return shadow_gaussian_lut[16]; + + int32_t scaled = t; + int32_t index = + (int32_t) (((int64_t) scaled * 16 + 0x8000) >> 16); /* rounded */ + if (index < 0) + index = 0; + else if (index > 16) + index = 16; + return shadow_gaussian_lut[index]; +} + +/* Renders a shadow effect that emulates the CSS 'box-shadow' property. + * + * This function uses precomputed fixed-point lookup tables for performance, + * approximating a Gaussian blur to create a soft shadow effect. The shadow's + * appearance is controlled by several compile-time options: + * - CONFIG_HORIZONTAL_OFFSET : Simulates the 'offset-x' value in CSS, + * determining the horizontal displacement of the shadow. + * - CONFIG_VERTICAL_OFFSET : Simulates the 'offset-y' value, controlling the + * vertical displacement. + * - CONFIG_SHADOW_BLUR : Approximates the 'blur-radius', defining the softness + * and extent of the shadow's edge. + * + * Limitations: + * - This implementation does not support the 'spread-radius' or 'inset' + * keywords from the CSS 'box-shadow' specification. The shadow is always an + * outer shadow, and its initial size is based on the window dimensions. + * - The shadow quality and performance are tied to the fixed-point precision + * and the size of the lookup table (i.e., 'shadow_gaussian_lut'). + */ +void twin_shadow_border(twin_pixmap_t *shadow, twin_argb32_t color) { - twin_coord_t right_edge = (*shadow).width - (*shadow).window->shadow_x, - bottom_edge = (*shadow).height - (*shadow).window->shadow_y, - right_span = right_edge, clip, corner_offset, y_start, - offset = min(offset_x, offset_y); + twin_coord_t win_width = shadow->width - shadow->window->shadow_x; + twin_coord_t win_height = shadow->height - shadow->window->shadow_y; + twin_coord_t y_start; + twin_a8_t base_alpha = (color >> 24) & 0xFF; + twin_argb32_t base_rgb = color & 0x00FFFFFF; + + if (!base_alpha) + return; + + /* Title-style windows leave the frame untouched by the blur. */ switch (shadow->window->style) { - /* - * Draw a black border starting from the top y position of the window's - * client area plus CONFIG_SHADOW_BLUR / 2 + 1, to prevent twin_stack_blur() - * from blurring shadows over the window frame. - */ case TwinWindowApplication: + /* Start below title bar to avoid blurring over window frame */ y_start = TWIN_TITLE_HEIGHT + CONFIG_SHADOW_BLUR / 2 + 1; break; case TwinWindowPlain: @@ -177,43 +232,275 @@ void twin_shadow_border(twin_pixmap_t *shadow, break; } - for (twin_coord_t y = y_start; y < (*shadow).height; y++) { - /* 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 = min((twin_coord_t) (y - y_start), offset_x); - twin_cover(shadow, color, right_edge, y, clip); - } else { - /* Calculate the range of the corner. */ - right_span++; + const twin_coord_t x_offset = CONFIG_HORIZONTAL_OFFSET; + const twin_coord_t y_offset = CONFIG_VERTICAL_OFFSET; + const twin_coord_t fade_tail = CONFIG_SHADOW_FADE_TAIL; + const twin_coord_t lut_x_len = x_offset + fade_tail; + const twin_coord_t lut_y_len = y_offset + fade_tail; + twin_coord_t right_extent = shadow->window->shadow_x; + twin_coord_t bottom_extent = shadow->window->shadow_y; + bool is_argb32 = (shadow->format == TWIN_ARGB32); + + if (right_extent <= 0 && bottom_extent <= 0) + return; + + if (right_extent > lut_x_len) + right_extent = lut_x_len; + if (bottom_extent > lut_y_len) + bottom_extent = lut_y_len; + + /* Clear stale shadow pixels before repainting the mask. */ + if (shadow->window->shadow_x > 0) { + for (twin_coord_t y = 0; y < win_height; y++) { + twin_pointer_t clear = twin_pixmap_pointer(shadow, win_width, y); + if (is_argb32) { + memset( + clear.argb32, 0, + (size_t) shadow->window->shadow_x * sizeof(*clear.argb32)); + } else { + for (twin_coord_t x = 0; x < shadow->window->shadow_x; x++) + clear.argb32[x] = 0; + } } - /* Render the bottom edge of the shadow border. */ - if (y >= bottom_edge && y < bottom_edge + offset_y) { - /* - * Create a shadow with a diagonal shape, extending from the - * top-left to the bottom-right. - */ - clip = max(0, y - bottom_edge); - twin_cover(shadow, color, clip, y, right_edge - clip); - /* Render the bottom-right corner of the shadow border. */ - /* - * Handle the case where the vertical shadow offset is larger than - * the horizontal shadow offset. - */ - corner_offset = - min((twin_coord_t) (right_span - right_edge), offset); - for (twin_coord_t i = 0; i < corner_offset; i++) { - /* The corner's pixels are symmetrical to the diagonal. */ - twin_cover(shadow, color, right_edge + i, y, 1); - twin_cover(shadow, color, right_edge + (y - bottom_edge), - bottom_edge + i, 1); + } + if (shadow->window->shadow_y > 0) { + for (twin_coord_t y = win_height; y < shadow->height; y++) { + twin_pointer_t clear = twin_pixmap_pointer(shadow, 0, y); + if (is_argb32) { + memset(clear.argb32, 0, + (size_t) shadow->width * sizeof(*clear.argb32)); + } else { + for (twin_coord_t x = 0; x < shadow->width; x++) + clear.argb32[x] = 0; + } + } + } + + /* Fixed-point lookup tables for edge falloffs (small: <= 10 entries). */ +#if CONFIG_HORIZONTAL_OFFSET <= 0 || CONFIG_VERTICAL_OFFSET <= 0 +#error "Drop shadow offsets must be positive." +#endif + twin_a8_t alpha_lut_x[SHADOW_LUT_X_LEN]; /* Right-edge fade LUT. */ + twin_a8_t alpha_lut_y[SHADOW_LUT_Y_LEN]; /* Bottom fade LUT. */ + + twin_fixed_t step_x = + (x_offset > 1) ? (TWIN_FIXED_ONE / (x_offset - 1)) : 0; + twin_fixed_t tx = 0; + for (twin_coord_t i = 0; i < x_offset; i++) { + twin_fixed_t weight = shadow_gaussian_weight(tx); + alpha_lut_x[i] = (twin_a8_t) ((weight * base_alpha) >> 16); + if (x_offset > 1 && tx < TWIN_FIXED_ONE) + tx += step_x; + } + for (twin_coord_t i = x_offset; i < lut_x_len; i++) + alpha_lut_x[i] = 0; + + bool reuse_alpha_y = false; + if (shadow_alpha_y_cache_valid && shadow_alpha_y_cache_offset == y_offset && + shadow_alpha_y_cache_alpha == base_alpha) { + memcpy(alpha_lut_y, shadow_alpha_y_cache, + (size_t) y_offset * sizeof(*alpha_lut_y)); + reuse_alpha_y = true; + } + + if (!reuse_alpha_y) { + twin_fixed_t step_y = + (y_offset > 1) ? (TWIN_FIXED_ONE / (y_offset - 1)) : 0; + twin_fixed_t ty = 0; + for (twin_coord_t i = 0; i < y_offset; i++) { + twin_fixed_t weight = shadow_gaussian_weight(ty); + alpha_lut_y[i] = (twin_a8_t) ((weight * base_alpha) >> 16); + if (y_offset > 1 && ty < TWIN_FIXED_ONE) + ty += step_y; + } + if (y_offset > 0) { + memcpy(shadow_alpha_y_cache, alpha_lut_y, + (size_t) y_offset * sizeof(*alpha_lut_y)); + shadow_alpha_y_cache_offset = y_offset; + shadow_alpha_y_cache_alpha = base_alpha; + shadow_alpha_y_cache_valid = true; + } + } + for (twin_coord_t i = y_offset; i < lut_y_len; i++) + alpha_lut_y[i] = 0; + + /* Right edge strip */ + if (right_extent > 0 && y_start < shadow->height) { + twin_coord_t y_end = win_height; + if (y_end > shadow->height) + y_end = shadow->height; + for (twin_coord_t y = y_start; y < y_end; y++) { + if (is_argb32) { + twin_argb32_t *row = + (twin_argb32_t *) (shadow->p.b + y * shadow->stride); + twin_argb32_t *dst = row + win_width; + for (twin_coord_t i = 0; i < right_extent; i++) + dst[i] = (alpha_lut_x[i] << 24) | base_rgb; + } else { + twin_pointer_t dst = twin_pixmap_pointer(shadow, win_width, y); + for (twin_coord_t i = 0; i < right_extent; i++) + dst.argb32[i] = (alpha_lut_x[i] << 24) | base_rgb; + } + } + } + + /* Bottom edge strip (excluding the corner overlap). */ + if (bottom_extent > 0) { + twin_coord_t bottom_start = win_height; + if (bottom_start < 0) + bottom_start = 0; + twin_coord_t bottom_end = win_height + bottom_extent; + if (bottom_end > shadow->height) + bottom_end = shadow->height; + + twin_coord_t left_skip = x_offset; + if (left_skip > win_width) + left_skip = win_width; + + twin_coord_t bottom_width = win_width - left_skip - right_extent; + if (bottom_width < 0) + bottom_width = 0; + + twin_a8_t *alpha_bottom = NULL; + bool alpha_bottom_ready = false; + + if (bottom_width > 0) { + if (bottom_width > shadow_bottom_cache_capacity) { + twin_a8_t *newbuf = + realloc(shadow_bottom_cache_weights, bottom_width); + if (newbuf) { + shadow_bottom_cache_weights = newbuf; + shadow_bottom_cache_capacity = bottom_width; + shadow_bottom_cache_width = 0; + } + } + if (shadow_bottom_cache_weights && + shadow_bottom_cache_capacity >= bottom_width) { + alpha_bottom = shadow_bottom_cache_weights; + if (shadow_bottom_cache_width == bottom_width) + alpha_bottom_ready = true; + } + if (!alpha_bottom) + alpha_bottom = malloc(bottom_width); + + if (alpha_bottom && !alpha_bottom_ready) { + twin_fixed_t bottom_step = + (bottom_width > 1) ? (TWIN_FIXED_ONE / (bottom_width - 1)) + : 0; + twin_fixed_t u = 0; + for (twin_coord_t x = 0; x < bottom_width; x++) { + twin_fixed_t weight = shadow_gaussian_weight(u); + alpha_bottom[x] = (twin_a8_t) ((weight * 255) >> 16); + if (bottom_width > 1 && u < TWIN_FIXED_ONE) + u += bottom_step; + } + if (alpha_bottom == shadow_bottom_cache_weights) + shadow_bottom_cache_width = bottom_width; + } + } + + for (twin_coord_t y = bottom_start; y < bottom_end; y++) { + if (is_argb32) { + twin_argb32_t *row = + (twin_argb32_t *) (shadow->p.b + y * shadow->stride); + if (left_skip > 0) + memset(row, 0, (size_t) left_skip * sizeof(*row)); + + if (bottom_width <= 0) + continue; + + twin_coord_t dist_y = y - win_height; + twin_a8_t alpha_y = alpha_lut_y[dist_y]; + twin_argb32_t *dst = row + left_skip; + + if (alpha_bottom) { + for (twin_coord_t x = 0; x < bottom_width; x++) { + twin_a8_t alpha = (alpha_y * alpha_bottom[0]) >> 8; + dst[x] = (alpha << 24) | base_rgb; + } + } else { + twin_fixed_t bottom_step = + (bottom_width > 1) + ? (TWIN_FIXED_ONE / (bottom_width - 1)) + : 0; + twin_fixed_t u = 0; + for (twin_coord_t x = 0; x < bottom_width; x++) { + twin_fixed_t weight = shadow_gaussian_weight(u); + twin_a8_t alpha_x = (twin_a8_t) ((weight * 255) >> 16); + twin_a8_t alpha = (alpha_y * alpha_x) >> 8; + dst[x] = (alpha << 24) | base_rgb; + if (bottom_width > 1 && u < TWIN_FIXED_ONE) + u += bottom_step; + } + } + } else { + twin_pointer_t clear = twin_pixmap_pointer(shadow, 0, y); + for (twin_coord_t x = 0; x < left_skip; x++) + clear.argb32[x] = 0; + + if (bottom_width <= 0) + continue; + + twin_coord_t dist_y = y - win_height; + twin_a8_t alpha_y = alpha_lut_y[dist_y]; + twin_pointer_t dst = twin_pixmap_pointer(shadow, left_skip, y); + + if (alpha_bottom) { + for (twin_coord_t x = 0; x < bottom_width; x++) { + twin_a8_t alpha = (alpha_y * alpha_bottom[0]) >> 8; + dst.argb32[x] = (alpha << 24) | base_rgb; + } + } else { + twin_fixed_t bottom_step = + (bottom_width > 1) + ? (TWIN_FIXED_ONE / (bottom_width - 1)) + : 0; + twin_fixed_t u = 0; + for (twin_coord_t x = 0; x < bottom_width; x++) { + twin_fixed_t weight = shadow_gaussian_weight(u); + twin_a8_t alpha_x = (twin_a8_t) ((weight * 255) >> 16); + twin_a8_t alpha = (alpha_y * alpha_x) >> 8; + dst.argb32[x] = (alpha << 24) | base_rgb; + if (bottom_width > 1 && u < TWIN_FIXED_ONE) + u += bottom_step; + } + } + } + } + + if (alpha_bottom && alpha_bottom != shadow_bottom_cache_weights) + free(alpha_bottom); + } + + /* Bottom-right corner combines both falloffs. */ + if (right_extent > 0 && bottom_extent > 0) { + twin_coord_t corner_end = win_height + bottom_extent; + if (corner_end > shadow->height) + corner_end = shadow->height; + for (twin_coord_t y = win_height; y < corner_end; y++) { + twin_coord_t dist_y = y - win_height; + twin_a8_t alpha_y = alpha_lut_y[dist_y]; + if (is_argb32) { + twin_argb32_t *row = + (twin_argb32_t *) (shadow->p.b + y * shadow->stride); + twin_argb32_t *dst = row + win_width; + for (twin_coord_t i = 0; i < right_extent; i++) { + twin_a8_t alpha = (alpha_lut_x[i] * alpha_y) >> 8; + dst[i] = (alpha << 24) | base_rgb; + } + } else { + twin_pointer_t dst = twin_pixmap_pointer(shadow, win_width, y); + for (twin_coord_t i = 0; i < right_extent; i++) { + twin_a8_t alpha = (alpha_lut_x[i] * alpha_y) >> 8; + dst.argb32[i] = (alpha << 24) | base_rgb; + } } } } } + +#undef SHADOW_LUT_X_LEN +#undef SHADOW_LUT_Y_LEN #endif static twin_argb32_t _twin_apply_alpha(twin_argb32_t v) diff --git a/src/twin_private.h b/src/twin_private.h index 4a24f57e..24b11b6b 100644 --- a/src/twin_private.h +++ b/src/twin_private.h @@ -644,10 +644,7 @@ typedef struct twin_backend { * Add a shadow with the specified color, horizontal offset, and vertical * offset. */ -void twin_shadow_border(twin_pixmap_t *shadow, - twin_argb32_t color, - twin_coord_t shift_x, - twin_coord_t shift_y); +void twin_shadow_border(twin_pixmap_t *shadow, twin_argb32_t color); #endif /* utility */ diff --git a/src/window.c b/src/window.c index a8e1db15..e73603dd 100644 --- a/src/window.c +++ b/src/window.c @@ -18,7 +18,9 @@ #define TWIN_TITLE_HEIGHT 20 #define TWIN_RESIZE_SIZE ((TWIN_TITLE_HEIGHT + 4) / 5) #define TWIN_TITLE_BW ((TWIN_TITLE_HEIGHT + 11) / 12) -#define SHADOW_COLOR 0xff000000 + +/* Shadow color for CSS-style effect: semi-transparent black (50% opacity). */ +#define SHADOW_COLOR 0x80000000 twin_window_t *twin_window_create(twin_screen_t *screen, twin_format_t format, @@ -59,11 +61,12 @@ twin_window_t *twin_window_create(twin_screen_t *screen, window->client.right = width - right; window->client.bottom = height - bottom; #if defined(CONFIG_DROP_SHADOW) - /* Handle drop shadow. */ - /* - * Add a shadowed area to the pixel map of the window to create a drop - * shadow effect. This calculation method is based on the range of - * CONFIG_HORIZONTAL_OFFSET, CONFIG_VERTICAL_OFFSET, and CONFIG_SHADOW_BLUR. + /* Handle drop shadow. + * + * Allocate extra space in the pixmap for the drop shadow. The dimensions + * are calculated based on the configured horizontal and vertical offsets, + * plus a blur radius, to ensure sufficient area for the blurred shadow. + * This approach is part of emulating a CSS-like 'box-shadow' effect. */ window->shadow_x = 2 * CONFIG_HORIZONTAL_OFFSET + CONFIG_SHADOW_BLUR; window->shadow_y = 2 * CONFIG_VERTICAL_OFFSET + CONFIG_SHADOW_BLUR; @@ -391,8 +394,7 @@ static void twin_window_drop_shadow(twin_window_t *window) * dimensional appearance. */ /* The shift offset and color of the shadow can be selected by the user. */ - twin_shadow_border(active_pix, SHADOW_COLOR, CONFIG_VERTICAL_OFFSET, - CONFIG_HORIZONTAL_OFFSET); + twin_shadow_border(active_pix, SHADOW_COLOR); /* Add a blur effect to the shadow of the active window. */ /* Right side of the active window */