rickveloper commited on
Commit
a6e8352
·
verified ·
1 Parent(s): b2df211

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +28 -34
app.py CHANGED
@@ -10,7 +10,7 @@ CANDIDATE_FONTS = [
10
  def get_font(size: int):
11
  for p in CANDIDATE_FONTS:
12
  if os.path.exists(p):
13
- return ImageFont.truetype(p, size=size)
14
  return ImageFont.load_default()
15
 
16
  # ---------------- Style Presets ----------------
@@ -25,8 +25,13 @@ PRESETS = {
25
  "Synthwave Grid": "purple/indigo gradient sky, mountain silhouette, glowing sun, grid floor",
26
  }
27
 
28
- # ---------------- Utility: gradient fallback (fast, offline) ----------------
 
 
 
 
29
  def gradient_from_prompt(prompt: str, w=768, h=768):
 
30
  hsh = hashlib.sha256((prompt or "meme").encode()).hexdigest()
31
  c1 = tuple(int(hsh[i:i+2], 16) for i in (0, 2, 4))
32
  c2 = tuple(int(hsh[i:i+2], 16) for i in (6, 8, 10))
@@ -43,7 +48,6 @@ def gradient_from_prompt(prompt: str, w=768, h=768):
43
  px[x, y] = (r, g, b)
44
  return img
45
 
46
- # ---------------- Text helpers ----------------
47
  def wrap_lines(draw, text, img_w, font, stroke):
48
  lines = []
49
  max_chars = max(12, min(30, img_w // 30))
@@ -84,17 +88,16 @@ def smart_split_text(prompt: str):
84
  return " ".join(words[:mid]).upper(), " ".join(words[mid:]).upper()
85
  return p.upper(), ""
86
 
87
- # ---------------- Optional: call public FLUX Space ----------------
88
  def try_generate_with_flux(prompt: str, width: int, height: int):
89
  from gradio_client import Client
90
- # Using a popular public Space; if its API changes or rate-limits, we'll fallback.
91
  client = Client("black-forest-labs/FLUX.1-schnell")
 
92
  try:
93
  result = client.predict(prompt, width, height, api_name="/predict")
94
  if isinstance(result, list): result = result[0]
95
  return Image.open(result)
96
  except Exception as e:
97
- # last-chance alternate endpoint some Spaces expose
98
  try:
99
  result = client.predict(prompt, api_name="/run")
100
  if isinstance(result, list): result = result[0]
@@ -102,17 +105,21 @@ def try_generate_with_flux(prompt: str, width: int, height: int):
102
  except Exception:
103
  raise e
104
 
105
- # ---------------- Main pipeline ----------------
106
  def generate_and_meme(
107
  prompt, preset_name, use_ai, width, height,
108
  font_size, stroke_width, text_color, outline_color,
109
  align, top_nudge, bottom_nudge, use_prompt_for_text, top_text_manual, bottom_text_manual
110
  ):
111
- base = (prompt or "").strip()
 
 
 
 
112
  style_suffix = PRESETS.get(preset_name or "None", "")
113
  gen_prompt = (base + " " + style_suffix).strip()
114
 
115
- # 1) Image
116
  if use_ai:
117
  try:
118
  img = try_generate_with_flux(gen_prompt, width, height)
@@ -125,57 +132,48 @@ def generate_and_meme(
125
  draw = ImageDraw.Draw(img)
126
  w, h = img.size
127
 
128
- # 2) Meme text
129
  if use_prompt_for_text:
130
  top_text, bottom_text = smart_split_text(base)
131
  else:
132
  top_text, bottom_text = (top_text_manual or "").upper(), (bottom_text_manual or "").upper()
133
 
134
- # 3) Draw
135
- base_size = max(12, int((w * font_size) / 100))
136
  font = get_font(base_size)
137
  stroke = int(max(0, stroke_width))
138
 
139
- top_y = int(h * 0.03) + int(top_nudge)
140
  _, _ = draw_block(draw, top_text, w, top_y, font, text_color, outline_color, stroke, align=align)
141
 
142
  lines, heights = wrap_lines(draw, bottom_text, w, font, stroke)
143
  total_bottom_h = sum(heights) + (len(heights)-1) * int(font.size*0.25)
144
- bottom_y_start = int(h - total_bottom_h - h*0.03) - int(bottom_nudge)
145
  draw_block(draw, bottom_text, w, bottom_y_start, font, text_color, outline_color, stroke, align=align)
146
 
147
  return img
148
 
149
- # ---------------- Theme + Retro CSS ----------------
150
- THEME = gr.themes.Soft(
151
- primary_hue="indigo",
152
- secondary_hue="violet",
153
- neutral_hue="slate"
154
- )
155
 
156
  CUSTOM_CSS = """
157
  @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
158
-
159
  :root { --radius: 14px; }
160
  * { -webkit-tap-highlight-color: transparent; }
161
  body { background: radial-gradient(1200px 600px at 50% -10%, #0d1220 10%, #05060b 70%); }
162
  .gradio-container { max-width: 900px; margin: 0 auto; padding: 12px; }
163
  h2, p { text-align: center; color: #cde3ff; text-shadow: 0 0 10px rgba(80,120,255,.25); }
164
  h2 { font-family: 'Press Start 2P', system-ui, sans-serif; letter-spacing: 1px; font-size: 18px; }
165
- .crt {
166
- position: relative; border: 2px solid #2a3350; border-radius: 12px; overflow: hidden;
167
- box-shadow: 0 0 0 1px #0f1427 inset, 0 0 40px rgba(60,80,255,.25);
168
- }
169
- .crt::before {
170
- content: ""; position: absolute; inset: 0; pointer-events: none;
171
  background: repeating-linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.05) 1px, transparent 1px, transparent 3px);
172
- mix-blend-mode: overlay; opacity: .25;
173
- }
174
  label { color: #a9b7ff !important; }
175
  .gr-button { font-weight: 800; border-radius: 12px; }
176
  """
177
 
178
- # ---------------- App ----------------
179
  with gr.Blocks(theme=THEME, css=CUSTOM_CSS) as demo:
180
  gr.Markdown("<h2>🕹️ MEME LAB — RETRO EDITION</h2>"
181
  "<p>One prompt → generate image → auto meme text. Style presets for instant vibes.</p>")
@@ -185,7 +183,6 @@ with gr.Blocks(theme=THEME, css=CUSTOM_CSS) as demo:
185
  prompt = gr.Textbox(label="Your idea (one prompt)", value="cat typing on a laptop at midnight")
186
  preset = gr.Dropdown(choices=list(PRESETS.keys()), value="Retro Comic", label="Style preset")
187
  use_ai = gr.Checkbox(label="Use AI generator (public FLUX Space)", value=False)
188
-
189
  with gr.Row():
190
  width = gr.Slider(384, 1024, value=768, step=64, label="Width")
191
  height = gr.Slider(384, 1024, value=768, step=64, label="Height")
@@ -198,11 +195,9 @@ with gr.Blocks(theme=THEME, css=CUSTOM_CSS) as demo:
198
  align = gr.Radio(choices=["left", "center", "right"], value="center", label="Text alignment")
199
  font_size = gr.Slider(8, 24, value=12, step=1, label="Font size (% of width)")
200
  stroke_width = gr.Slider(0, 16, value=4, step=1, label="Outline thickness")
201
-
202
  with gr.Row():
203
  text_color = gr.ColorPicker(value="#FFFFFF", label="Text color")
204
  outline_color = gr.ColorPicker(value="#000000", label="Outline color")
205
-
206
  with gr.Row():
207
  top_nudge = gr.Slider(-300, 300, value=0, step=1, label="Top nudge (px)")
208
  bottom_nudge = gr.Slider(-300, 300, value=0, step=1, label="Bottom nudge (px)")
@@ -217,7 +212,6 @@ with gr.Blocks(theme=THEME, css=CUSTOM_CSS) as demo:
217
 
218
  generate.click(fn=generate_and_meme, inputs=inputs, outputs=out)
219
 
220
- # Quick-refresh when only style/text settings change
221
  for comp in [preset, use_prompt_for_text, top_text_manual, bottom_text_manual,
222
  font_size, stroke_width, text_color, outline_color, align, top_nudge, bottom_nudge]:
223
  comp.change(fn=generate_and_meme, inputs=inputs, outputs=out, show_progress=False)
 
10
  def get_font(size: int):
11
  for p in CANDIDATE_FONTS:
12
  if os.path.exists(p):
13
+ return ImageFont.truetype(p, size=int(size))
14
  return ImageFont.load_default()
15
 
16
  # ---------------- Style Presets ----------------
 
25
  "Synthwave Grid": "purple/indigo gradient sky, mountain silhouette, glowing sun, grid floor",
26
  }
27
 
28
+ # -------- helpers --------
29
+ def i(v): # safe int
30
+ try: return int(round(float(v)))
31
+ except: return int(v)
32
+
33
  def gradient_from_prompt(prompt: str, w=768, h=768):
34
+ w, h = i(w), i(h)
35
  hsh = hashlib.sha256((prompt or "meme").encode()).hexdigest()
36
  c1 = tuple(int(hsh[i:i+2], 16) for i in (0, 2, 4))
37
  c2 = tuple(int(hsh[i:i+2], 16) for i in (6, 8, 10))
 
48
  px[x, y] = (r, g, b)
49
  return img
50
 
 
51
  def wrap_lines(draw, text, img_w, font, stroke):
52
  lines = []
53
  max_chars = max(12, min(30, img_w // 30))
 
88
  return " ".join(words[:mid]).upper(), " ".join(words[mid:]).upper()
89
  return p.upper(), ""
90
 
91
+ # -------- Optional public Space call --------
92
  def try_generate_with_flux(prompt: str, width: int, height: int):
93
  from gradio_client import Client
 
94
  client = Client("black-forest-labs/FLUX.1-schnell")
95
+ width, height = i(width), i(height)
96
  try:
97
  result = client.predict(prompt, width, height, api_name="/predict")
98
  if isinstance(result, list): result = result[0]
99
  return Image.open(result)
100
  except Exception as e:
 
101
  try:
102
  result = client.predict(prompt, api_name="/run")
103
  if isinstance(result, list): result = result[0]
 
105
  except Exception:
106
  raise e
107
 
108
+ # -------- Main pipeline --------
109
  def generate_and_meme(
110
  prompt, preset_name, use_ai, width, height,
111
  font_size, stroke_width, text_color, outline_color,
112
  align, top_nudge, bottom_nudge, use_prompt_for_text, top_text_manual, bottom_text_manual
113
  ):
114
+ width, height = i(width), i(height)
115
+ top_nudge, bottom_nudge = i(top_nudge), i(bottom_nudge)
116
+ stroke_width = i(stroke_width)
117
+
118
+ base = (prompt or "").trim() if hasattr(str, "trim") else (prompt or "").strip()
119
  style_suffix = PRESETS.get(preset_name or "None", "")
120
  gen_prompt = (base + " " + style_suffix).strip()
121
 
122
+ # 1) image
123
  if use_ai:
124
  try:
125
  img = try_generate_with_flux(gen_prompt, width, height)
 
132
  draw = ImageDraw.Draw(img)
133
  w, h = img.size
134
 
135
+ # 2) text
136
  if use_prompt_for_text:
137
  top_text, bottom_text = smart_split_text(base)
138
  else:
139
  top_text, bottom_text = (top_text_manual or "").upper(), (bottom_text_manual or "").upper()
140
 
141
+ # 3) draw
142
+ base_size = max(12, int((w * float(font_size)) / 100))
143
  font = get_font(base_size)
144
  stroke = int(max(0, stroke_width))
145
 
146
+ top_y = int(h * 0.03) + top_nudge
147
  _, _ = draw_block(draw, top_text, w, top_y, font, text_color, outline_color, stroke, align=align)
148
 
149
  lines, heights = wrap_lines(draw, bottom_text, w, font, stroke)
150
  total_bottom_h = sum(heights) + (len(heights)-1) * int(font.size*0.25)
151
+ bottom_y_start = int(h - total_bottom_h - h*0.03) - bottom_nudge
152
  draw_block(draw, bottom_text, w, bottom_y_start, font, text_color, outline_color, stroke, align=align)
153
 
154
  return img
155
 
156
+ # -------- Theme + Retro CSS --------
157
+ THEME = gr.themes.Soft(primary_hue="indigo", secondary_hue="violet", neutral_hue="slate")
 
 
 
 
158
 
159
  CUSTOM_CSS = """
160
  @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
 
161
  :root { --radius: 14px; }
162
  * { -webkit-tap-highlight-color: transparent; }
163
  body { background: radial-gradient(1200px 600px at 50% -10%, #0d1220 10%, #05060b 70%); }
164
  .gradio-container { max-width: 900px; margin: 0 auto; padding: 12px; }
165
  h2, p { text-align: center; color: #cde3ff; text-shadow: 0 0 10px rgba(80,120,255,.25); }
166
  h2 { font-family: 'Press Start 2P', system-ui, sans-serif; letter-spacing: 1px; font-size: 18px; }
167
+ .crt { position: relative; border: 2px solid #2a3350; border-radius: 12px; overflow: hidden;
168
+ box-shadow: 0 0 0 1px #0f1427 inset, 0 0 40px rgba(60,80,255,.25); }
169
+ .crt::before { content: ""; position: absolute; inset: 0; pointer-events: none;
 
 
 
170
  background: repeating-linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.05) 1px, transparent 1px, transparent 3px);
171
+ mix-blend-mode: overlay; opacity: .25; }
 
172
  label { color: #a9b7ff !important; }
173
  .gr-button { font-weight: 800; border-radius: 12px; }
174
  """
175
 
176
+ # -------- App --------
177
  with gr.Blocks(theme=THEME, css=CUSTOM_CSS) as demo:
178
  gr.Markdown("<h2>🕹️ MEME LAB — RETRO EDITION</h2>"
179
  "<p>One prompt → generate image → auto meme text. Style presets for instant vibes.</p>")
 
183
  prompt = gr.Textbox(label="Your idea (one prompt)", value="cat typing on a laptop at midnight")
184
  preset = gr.Dropdown(choices=list(PRESETS.keys()), value="Retro Comic", label="Style preset")
185
  use_ai = gr.Checkbox(label="Use AI generator (public FLUX Space)", value=False)
 
186
  with gr.Row():
187
  width = gr.Slider(384, 1024, value=768, step=64, label="Width")
188
  height = gr.Slider(384, 1024, value=768, step=64, label="Height")
 
195
  align = gr.Radio(choices=["left", "center", "right"], value="center", label="Text alignment")
196
  font_size = gr.Slider(8, 24, value=12, step=1, label="Font size (% of width)")
197
  stroke_width = gr.Slider(0, 16, value=4, step=1, label="Outline thickness")
 
198
  with gr.Row():
199
  text_color = gr.ColorPicker(value="#FFFFFF", label="Text color")
200
  outline_color = gr.ColorPicker(value="#000000", label="Outline color")
 
201
  with gr.Row():
202
  top_nudge = gr.Slider(-300, 300, value=0, step=1, label="Top nudge (px)")
203
  bottom_nudge = gr.Slider(-300, 300, value=0, step=1, label="Bottom nudge (px)")
 
212
 
213
  generate.click(fn=generate_and_meme, inputs=inputs, outputs=out)
214
 
 
215
  for comp in [preset, use_prompt_for_text, top_text_manual, bottom_text_manual,
216
  font_size, stroke_width, text_color, outline_color, align, top_nudge, bottom_nudge]:
217
  comp.change(fn=generate_and_meme, inputs=inputs, outputs=out, show_progress=False)