Spaces:
Build error
Build error
import gradio as gr | |
import numpy as np | |
import io | |
import PIL.Image as Image | |
from rembg import remove | |
from PIL import ImageDraw, ImageFont | |
import textwrap | |
# Function to remove background and place text behind person | |
def text_behind_image(input_image, text, text_color, font_size, text_opacity): | |
if input_image is None: | |
return None | |
# Convert color hex to RGB | |
try: | |
r = int(text_color[1:3], 16) | |
g = int(text_color[3:5], 16) | |
b = int(text_color[5:7], 16) | |
text_color_rgb = (r, g, b) | |
except: | |
text_color_rgb = (255, 255, 255) # Default to white | |
try: | |
# Open the image | |
img = Image.fromarray(input_image) | |
# Get dimensions | |
width, height = img.size | |
# Create a new image with white background (this will be the canvas) | |
background = Image.new('RGBA', (width, height), (255, 255, 255, 255)) | |
# Create a drawing context for the text | |
draw = ImageDraw.Draw(background) | |
# Determine font size (relative to image size) | |
font_size = int(min(width, height) * (font_size / 100)) # Convert percentage to actual size | |
if font_size < 10: # Ensure font is at least 10 pixels | |
font_size = 10 | |
# Try to load a nice font, fall back to default if not available | |
try: | |
# Try different common fonts | |
font_paths = [ | |
"arial.ttf", | |
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", | |
"/usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf" | |
] | |
font = None | |
for font_path in font_paths: | |
try: | |
font = ImageFont.truetype(font_path, font_size) | |
break | |
except: | |
continue | |
if font is None: | |
font = ImageFont.load_default() | |
except: | |
font = ImageFont.load_default() | |
# Prepare the text - make it ALL CAPS for better visual impact | |
text = text.upper() | |
# Fill the entire background with repeating text pattern | |
# Word wrap the text to fit within the image width | |
margin = 20 | |
max_chars = max(10, int(width / (font_size/2)) - 2*margin) | |
wrapper = textwrap.TextWrapper(width=max_chars) | |
word_list = wrapper.wrap(text) | |
# If text is too short, repeat it to fill the background | |
if len(word_list) == 1 and len(text) < 10: | |
repeated_text = (text + " ") * 10 | |
word_list = [] | |
for i in range(0, height, font_size + 5): | |
word_list.append(repeated_text) | |
# Calculate text block height | |
line_spacing = int(font_size * 1.2) | |
text_height = len(word_list) * line_spacing | |
# Position text in the center | |
y_text = (height - text_height) // 2 | |
# Draw text with specified opacity | |
for line in word_list: | |
# Get line width | |
try: | |
line_width = font.getbbox(line)[2] - font.getbbox(line)[0] | |
except: | |
# Fallback method to estimate width | |
line_width = len(line) * (font_size // 2) | |
# Center the line | |
x_text = (width - line_width) // 2 | |
# Draw text with specified opacity | |
text_color_with_opacity = text_color_rgb + (int(text_opacity * 255),) | |
draw.text((x_text, y_text), line, font=font, fill=text_color_with_opacity) | |
y_text += line_spacing | |
# Ensure the text is very visible by drawing it multiple times with different Y positions | |
if len(word_list) < 5: | |
for offset in [-line_spacing*2, line_spacing*2]: | |
y_text = (height - text_height) // 2 + offset | |
for line in word_list: | |
try: | |
line_width = font.getbbox(line)[2] - font.getbbox(line)[0] | |
except: | |
line_width = len(line) * (font_size // 2) | |
x_text = (width - line_width) // 2 | |
draw.text((x_text, y_text), line, font=font, fill=text_color_with_opacity) | |
y_text += line_spacing | |
# Remove background from the original image to get the person silhouette | |
# Use the u2net_human_seg model specifically for better human segmentation | |
try: | |
person_img = remove(img, model_name="u2net_human_seg") | |
except: | |
# Fallback to default model if human_seg not available | |
person_img = remove(img) | |
# Composite the images: text in background, then person on top | |
final_image = Image.alpha_composite(background.convert('RGBA'), person_img.convert('RGBA')) | |
# Convert back to RGB for display | |
return np.array(final_image.convert('RGB')) | |
except Exception as e: | |
print(f"Error processing image: {e}") | |
return input_image # Return original image on error | |
# Create Gradio interface | |
with gr.Blocks(title="Text Behind Image") as demo: | |
gr.Markdown("# Text Behind Image") | |
gr.Markdown("Upload an image with a person and add text behind them") | |
with gr.Row(): | |
with gr.Column(): | |
input_image = gr.Image(label="Upload Image", type="numpy") | |
text_input = gr.Textbox(label="Text to place behind", placeholder="Enter text here...") | |
with gr.Row(): | |
text_color = gr.ColorPicker(label="Text Color", value="#FFFFFF") | |
font_size = gr.Slider(label="Font Size (%)", minimum=1, maximum=30, value=10, step=1) | |
text_opacity = gr.Slider(label="Text Opacity", minimum=0.1, maximum=1.0, value=0.8, step=0.1) | |
submit_btn = gr.Button("Generate", variant="primary") | |
with gr.Column(): | |
output_image = gr.Image(label="Result", type="numpy") | |
submit_btn.click( | |
fn=text_behind_image, | |
inputs=[input_image, text_input, text_color, font_size, text_opacity], | |
outputs=output_image | |
) | |
gr.Markdown("## How it works") | |
gr.Markdown("1. Upload an image with a person") | |
gr.Markdown("2. Enter the text you want to place behind the person") | |
gr.Markdown("3. Customize text color, size, and opacity") | |
gr.Markdown("4. Click 'Generate' to create your image") | |
# Launch the app | |
demo.launch() |