Glassmorphism Generator — C source
Create frosted-glass CSS - backdrop-blur, translucent background, subtle border, and soft shadow - with a copy-ready snippet and live preview.
This is the C implementation — the same logic the interactive tool runs, in a shareable, citable form.
/* =============================================================================
* glassmorphism-generator.c — CosmoDev polyglot showcase port of the
* `glassmorphism-generator` tool
* -----------------------------------------------------------------------------
* Language : C (C11, standard library only)
* Source: ported from src/lib/glassmorphism.ts (the canonical, live
* TypeScript lib); mirrors
* src/tool-sources/glassmorphism-generator/{python.py,rust.rs}
* License : display source — part of CosmoDev's polyglot tool pages
* (dev.cosmolabs.org). Shown verbatim alongside the JS/TS/Go/Rust/
* Python ports and the other language ports.
* -----------------------------------------------------------------------------
* Builds a frosted-glass CSS block from blur / opacity / tint / radius /
* border_opacity (+ optional saturation). Hex tints (#rgb / #rrggbb) are
* validated by hand with isxdigit (no regex — stdlib only); opacity is
* clamped to [0,1]; the alpha channel is rounded with
* floor(x*100 + 0.5)/100 — JS Math.round (half-up) semantics — and numbers
* are rendered like JS template literals (10.0 -> "10", 0.3 -> "0.3") via a
* %g loop that stops at the shortest plain-decimal round-tripping form, so
* the output matches the live TypeScript lib byte-for-byte.
*
* All strings live in fixed buffers owned by the glassmorphism_css struct
* the caller owns — no heap, no globals.
* =============================================================================
*/
#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Configuration for one glassmorphism declaration block. `has_saturation`
* selects between `saturation` and the lib default of 180%. */
typedef struct {
double blur;
double opacity;
const char *tint;
double radius;
double border_opacity;
int has_saturation;
double saturation;
} glassmorphism_config;
/* The rendered glassmorphism block plus its full CSS text. Fixed buffers:
* each field holds the longest realistic declaration with margin (each
* number renders as at most ~25 characters), full_css holds all six lines. */
typedef struct {
char background[96];
char backdrop_filter[96];
char webkit_backdrop_filter[96];
char border[96];
char border_radius[64];
char box_shadow[64];
char full_css[512];
} glassmorphism_css;
/* --- number formatting ----------------------------------------------------- */
/* Render a double the way JavaScript's template literal does — shortest
* round-tripping plain decimal, no trailing ".0" on whole numbers. C has no
* direct shortest format, so ask %g for increasing precision until the output
* parses back to the same double AND carries no exponent marker: %g alone
* would render 10.0 as "1e+01" at precision 1, but JavaScript renders 10 as
* "10" (JS switches to exponent form only outside 1e-7..1e21, a domain these
* clamped UI values never reach). %g already drops trailing zeros. */
static void format_number(double x, char *buf, size_t bufsz)
{
char tmp[64];
for (int prec = 1; prec <= 17; prec++) {
snprintf(tmp, sizeof tmp, "%.*g", prec, x);
if (strtod(tmp, NULL) == x && strchr(tmp, 'e') == NULL && strchr(tmp, 'E') == NULL) {
break;
}
}
snprintf(buf, bufsz, "%s", tmp);
}
/* --- hex parsing ------------------------------------------------------------ */
/* True when `s` is exactly `len` characters, all ASCII hex digits. */
static int is_hex_run(const char *s, size_t len)
{
if (strlen(s) != len) {
return 0;
}
for (size_t i = 0; i < len; i++) {
if (!isxdigit((unsigned char)s[i])) {
return 0;
}
}
return 1;
}
/* Parse one hex digit (0-9, a-f, A-F) into 0..15; 0 on a non-digit. */
static int hex_val(char c)
{
return (int)strtoul((char[]){ c, '\0' }, NULL, 16);
}
/* Parse #rgb or #rrggbb into (r, g, b); 0 on a bad color, 1 on success.
* A 3-char hex expands each digit (e.g. "abc" -> "aabbcc"), like CSS.
* Exactly one leading '#' is stripped, matching the lib's /^#/ replace. */
int glassmorphism_hex_to_rgb(const char *hex, int *r, int *g, int *b)
{
/* Trim ASCII whitespace first — the set JS String.prototype.trim() strips. */
while (*hex != '\0' && isspace((unsigned char)*hex)) {
hex++;
}
size_t len = strlen(hex);
while (len > 0 && isspace((unsigned char)hex[len - 1])) {
len--;
}
size_t skip = (len > 0 && hex[0] == '#') ? 1u : 0u;
const char *p = hex + skip;
size_t n = len - skip;
if (n == 3 && is_hex_run(p, 3)) {
*r = hex_val(p[0]) * 16 + hex_val(p[0]); /* "a" -> 0xaa — CSS expansion */
*g = hex_val(p[1]) * 16 + hex_val(p[1]);
*b = hex_val(p[2]) * 16 + hex_val(p[2]);
return 1;
}
if (n == 6 && is_hex_run(p, 6)) {
char pair[3];
pair[2] = '\0';
pair[0] = p[0];
pair[1] = p[1];
*r = (int)strtoul(pair, NULL, 16);
pair[0] = p[2];
pair[1] = p[3];
*g = (int)strtoul(pair, NULL, 16);
pair[0] = p[4];
pair[1] = p[5];
*b = (int)strtoul(pair, NULL, 16);
return 1;
}
return 0;
}
/* --- rgba formatting -------------------------------------------------------- */
/* Clamp to [0,1], round with JS Math.round (half-up), and render. */
static void format_alpha(double a, char *buf, size_t bufsz)
{
double c = a < 0.0 ? 0.0 : (a > 1.0 ? 1.0 : a);
format_number(floor(c * 100.0 + 0.5) / 100.0, buf, bufsz);
}
/* Format rgba(), clamping + 2-decimal-rounding the alpha. */
static void format_rgba(int r, int g, int b, double a, char *buf, size_t bufsz)
{
char alpha[32];
format_alpha(a, alpha, sizeof alpha);
snprintf(buf, bufsz, "rgba(%d, %d, %d, %s)", r, g, b, alpha);
}
/* --- the builder ------------------------------------------------------------- */
/* Build the full glassmorphism CSS for a config. */
glassmorphism_css glassmorphism_build(const glassmorphism_config *cfg)
{
glassmorphism_css out;
memset(&out, 0, sizeof out);
double blur = fmax(cfg->blur, 0.0);
double opacity = fmin(fmax(cfg->opacity, 0.0), 1.0);
double radius = fmax(cfg->radius, 0.0);
double border_opacity = fmin(fmax(cfg->border_opacity, 0.0), 1.0);
double saturation = cfg->has_saturation ? cfg->saturation : 180.0;
int r = 255, g = 255, b = 255;
(void)glassmorphism_hex_to_rgb(cfg->tint, &r, &g, &b);
char blur_s[32], sat_s[32], radius_s[32];
format_number(blur, blur_s, sizeof blur_s);
format_number(saturation, sat_s, sizeof sat_s);
format_number(radius, radius_s, sizeof radius_s);
format_rgba(r, g, b, opacity, out.background, sizeof out.background);
snprintf(out.backdrop_filter, sizeof out.backdrop_filter,
"blur(%spx) saturate(%s%%)", blur_s, sat_s);
char border_rgba[64];
format_rgba(255, 255, 255, border_opacity, border_rgba, sizeof border_rgba);
snprintf(out.border, sizeof out.border, "1px solid %s", border_rgba);
snprintf(out.border_radius, sizeof out.border_radius, "%spx", radius_s);
snprintf(out.box_shadow, sizeof out.box_shadow, "%s",
"0 8px 32px 0 rgba(31, 38, 135, 0.37)");
/* Both prefixed and unprefixed backdrop-filter carry the same value. */
memcpy(out.webkit_backdrop_filter, out.backdrop_filter, sizeof out.backdrop_filter);
snprintf(out.full_css, sizeof out.full_css,
"background: %s;\n"
"-webkit-backdrop-filter: %s;\n"
"backdrop-filter: %s;\n"
"border-radius: %s;\n"
"border: %s;\n"
"box-shadow: %s;",
out.background, out.webkit_backdrop_filter, out.backdrop_filter,
out.border_radius, out.border, out.box_shadow);
return out;
}
int main(void)
{
glassmorphism_config cfg = {
.blur = 10.0,
.opacity = 0.3,
.tint = "#ffffff",
.radius = 12.0,
.border_opacity = 0.2,
.has_saturation = 0,
.saturation = 0.0,
};
glassmorphism_css css = glassmorphism_build(&cfg);
puts(css.full_css);
return 0;
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →