AI Image Generator — C source
Turn a text prompt into a 1024×1024 image with FLUX.1 [schnell] on Cloudflare Workers AI. No account, no API key — rate-limited for fair use. Your prompt goes to the model through our Worker; we store no prompts and no images.
This is the C implementation — the same logic the interactive tool runs, in a shareable, citable form.
/* AI Image Generator — prompt validation, gate verdicts, and ?prompt= URL
* state for FLUX.1-schnell (C11, stdlib only). Port of src/lib/image-gen.ts. */
#include <ctype.h>
#include <stdint.h>
#include <string.h>
#define MODEL "@cf/black-forest-labs/flux-1-schnell" /* + 4 steps = 57.6 n/img */
enum { STEPS = 4, PROMPT_MAX_CHARS = 600, IP_HOUR_CAP = 10, DAILY_CAP = 100 };
typedef enum { PROMPT_OK, PROMPT_EMPTY, PROMPT_TOO_LONG, PROMPT_CTRL } prompt_reason;
typedef enum { GATE_OK, GATE_IP, GATE_DAILY } gate_error;
/* Normalize CRLF/CR to \n and trim in one pass (out holds strlen(raw) bytes;
* length is bytes — stricter than the TS char count), then reject control
* chars except \n (C1 is exactly lead 0xC2 + continuation 0x80..0x9F). */
prompt_reason validate_prompt(const char *raw, char *out)
{
size_t n = 0; int started = 0;
for (size_t i = 0; raw[i]; i++) {
char c = raw[i];
if (c == '\r') { if (raw[i + 1] == '\n') continue; c = '\n'; }
if (!started && isspace((unsigned char) c)) continue;
started = 1;
out[n++] = c;
}
while (n > 0 && isspace((unsigned char) out[n - 1])) n--;
if (n == 0) return PROMPT_EMPTY;
if (n > PROMPT_MAX_CHARS) return PROMPT_TOO_LONG;
for (size_t i = 0; i < n; i++) {
unsigned char c = (unsigned char) out[i];
int cont = (c & 0xC0) == 0x80; /* UTF-8 continuation byte */
int c1 = c == 0xC2 && i + 1 < n && ((unsigned char) out[i + 1] & 0xE0) == 0x80; /* C1 */
if (!cont && (c1 || (c < 0x20 && c != '\n') || c == 0x7F)) return PROMPT_CTRL;
}
out[n] = '\0';
return PROMPT_OK;
}
/* Daily cap wins (resets at UTC midnight); the IP window is a fixed hour. */
gate_error check_gate(uint32_t ip_count, uint32_t daily_count, uint64_t now_ms, uint32_t *retry)
{
if (daily_count >= DAILY_CAP) {
*retry = (uint32_t) ((86400000ULL - now_ms % 86400000ULL + 59999ULL) / 60000ULL);
return GATE_DAILY;
}
if (ip_count >= IP_HOUR_CAP) { *retry = 60; return GATE_IP; }
return GATE_OK;
}
static void percent_encode(const char *s, size_t n, char *out)
{
static const char hex[] = "0123456789ABCDEF";
size_t k = 0;
for (size_t i = 0; i < n; i++) {
unsigned char c = (unsigned char) s[i];
if (isalnum(c) || strchr("-._~", c) != NULL) out[k++] = (char) c;
else { out[k++] = '%'; out[k++] = hex[c >> 4]; out[k++] = hex[c & 0xF]; }
}
out[k] = '\0';
}
/* Encode the shareable state: "" when there is nothing to share. */
void encode_url_state(const char *prompt, size_t n, char *out)
{
if (n == 0) { out[0] = '\0'; return; }
memcpy(out, "?prompt=", 8);
percent_encode(prompt, n, out + 8);
}
static int hexval(char c) { return c <= '9' ? c - '0' : (c | 32) - 'a' + 10; }
/* Inverse of encode_url_state — decode the leading ?prompt= pair (well-formed %XX assumed). */
void decode_url_state(const char *search, char *out)
{
if (strncmp(search, "?prompt=", 8) != 0) { out[0] = '\0'; return; }
size_t k = 0;
for (const char *v = search + 8; *v && *v != '&'; v++) {
if (*v == '%' && v[1] && v[2]) {
out[k++] = (char) ((hexval(v[1]) << 4) | hexval(v[2])); v += 2;
} else out[k++] = *v;
}
out[k] = '\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 →