AI Image Generator — Java 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 Java implementation — the same logic the interactive tool runs, in a shareable, citable form.
// AI Image Generator — prompt validation, usage-gate verdicts, and ?prompt=
// URL state for FLUX.1-schnell image generation.
//
// Language: Java (Java 17, standard library only)
// Source: CosmoDev polyglot showcase port, from src/lib/image-gen.ts.
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneOffset;
public final class ImageGen {
public static final String MODEL = "@cf/black-forest-labs/flux-1-schnell";
public static final int STEPS = 4; // flux-1-schnell's design point -> 57.6 neurons/image
public static final int PROMPT_MAX_CHARS = 600; // UX clamp, below the 2048 max
public static final int IP_HOUR_CAP = 10;
public static final int DAILY_CAP = 100;
/** ok ? prompt : null; reason is null when ok ("empty"|"tooLong"|"controlChars"). */
public record PromptResult(boolean ok, String prompt, String reason) {}
/** ok ? null error : "RATE_LIMITED_IP"|"RATE_LIMITED_DAILY" + retry minutes. */
public record GateVerdict(boolean ok, String error, int retryAfterMinutes) {}
/** Normalize CRLF/CR to \n, trim, enforce 1..600 chars, and reject control
* chars except \n (C0 minus U+000A, DEL, C1). */
public static PromptResult validatePrompt(String raw) {
String prompt = raw.replace("\r\n", "\n").replace('\r', '\n').strip();
if (prompt.isEmpty()) return new PromptResult(false, null, "empty");
if (prompt.codePointCount(0, prompt.length()) > PROMPT_MAX_CHARS) {
return new PromptResult(false, null, "tooLong");
}
for (int i = 0; i < prompt.length(); i++) {
char c = prompt.charAt(i);
boolean isCtrl = c < '\n' || (c > '\n' && c < ' ') || (c >= 0x7F && c <= 0x9F);
if (isCtrl) return new PromptResult(false, null, "controlChars");
}
return new PromptResult(true, prompt, null);
}
/** Minutes (>= 1) until the next UTC midnight — the daily window reset. */
private static int minutesToUtcMidnight(Instant now) {
var midnight = LocalDate.ofInstant(now, ZoneOffset.UTC).plusDays(1)
.atStartOfDay().toInstant(ZoneOffset.UTC);
return Math.max(1, (int) Math.ceil(Duration.between(now, midnight).toMillis() / 60_000.0));
}
/** Daily cap wins: the site-global budget resets at UTC midnight. */
public static GateVerdict checkGate(int ipCount, int dailyCount, Instant now) {
if (dailyCount >= DAILY_CAP) {
return new GateVerdict(false, "RATE_LIMITED_DAILY", minutesToUtcMidnight(now));
}
if (ipCount >= IP_HOUR_CAP) return new GateVerdict(false, "RATE_LIMITED_IP", 60);
return new GateVerdict(true, null, 0);
}
/** Encode the shareable state: "" when there is nothing to share.
* URLEncoder is form-encoded (+ for spaces) — swap in %20 to match the
* TS encodeURIComponent. */
public static String encodeUrlState(String prompt) {
if (prompt.isEmpty()) return "";
return "?prompt=" + URLEncoder.encode(prompt, StandardCharsets.UTF_8).replace("+", "%20");
}
/** Inverse of encodeUrlState — a missing param decodes to "". */
public static String decodeUrlState(String search) {
for (String pair : search.replaceFirst("^\\?", "").split("&")) {
if (pair.startsWith("prompt=")) {
String value = pair.substring("prompt=".length()).replace("%20", "+");
return URLDecoder.decode(value, StandardCharsets.UTF_8);
}
}
return "";
}
private ImageGen() {}
}
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 →