|
|
import gradio as gr |
|
|
import numpy as np |
|
|
from PIL import Image, ImageDraw, ImageFont |
|
|
import io |
|
|
import os |
|
|
from typing import Optional, Tuple, List |
|
|
import re |
|
|
|
|
|
|
|
|
def detect_text_in_image(image: np.ndarray) -> List[str]: |
|
|
"""Mock function to detect existing text in image""" |
|
|
|
|
|
|
|
|
return ["Sample text detected", "Another text element"] |
|
|
|
|
|
def add_text_to_image( |
|
|
image: np.ndarray, |
|
|
new_text: str, |
|
|
text_position: str, |
|
|
font_size: int, |
|
|
text_color: str, |
|
|
background_color: str, |
|
|
detect_existing: bool, |
|
|
accessibility_description: str |
|
|
) -> Tuple[np.ndarray, str, str]: |
|
|
""" |
|
|
Add text overlay to image with accessibility features |
|
|
""" |
|
|
if image is None: |
|
|
return None, "Please upload an image first", "" |
|
|
|
|
|
|
|
|
pil_image = Image.fromarray(image.astype('uint8'), 'RGB') |
|
|
|
|
|
|
|
|
existing_text_info = "" |
|
|
if detect_existing: |
|
|
try: |
|
|
detected_texts = detect_text_in_image(image) |
|
|
if detected_texts: |
|
|
existing_text_info = "Detected existing text:\n" + "\n".join(f"• {text}" for text in detected_texts) |
|
|
else: |
|
|
existing_text_info = "No existing text detected in the image." |
|
|
except Exception as e: |
|
|
existing_text_info = f"Text detection failed: {str(e)}" |
|
|
|
|
|
|
|
|
draw = ImageDraw.Draw(pil_image) |
|
|
|
|
|
|
|
|
try: |
|
|
font = ImageFont.truetype("arial.ttf", font_size) |
|
|
except: |
|
|
font = ImageFont.load_default() |
|
|
|
|
|
|
|
|
img_width, img_height = pil_image.size |
|
|
|
|
|
|
|
|
text_bbox = draw.textbbox((0, 0), new_text, font=font) |
|
|
text_width = text_bbox[2] - text_bbox[0] |
|
|
text_height = text_bbox[3] - text_bbox[1] |
|
|
|
|
|
position_map = { |
|
|
"top-left": (10, 10), |
|
|
"top-center": ((img_width - text_width) // 2, 10), |
|
|
"top-right": (img_width - text_width - 10, 10), |
|
|
"center-left": (10, (img_height - text_height) // 2), |
|
|
"center": ((img_width - text_width) // 2, (img_height - text_height) // 2), |
|
|
"center-right": (img_width - text_width - 10, (img_height - text_height) // 2), |
|
|
"bottom-left": (10, img_height - text_height - 10), |
|
|
"bottom-center": ((img_width - text_width) // 2, img_height - text_height - 10), |
|
|
"bottom-right": (img_width - text_width - 10, img_height - text_height - 10) |
|
|
} |
|
|
|
|
|
x, y = position_map.get(text_position, position_map["bottom-center"]) |
|
|
|
|
|
|
|
|
if background_color != "transparent": |
|
|
|
|
|
if background_color.startswith('#'): |
|
|
bg_rgb = tuple(int(background_color[i:i+2], 16) for i in (1, 3, 5)) |
|
|
else: |
|
|
bg_rgb = (0, 0, 0) |
|
|
|
|
|
|
|
|
padding = 5 |
|
|
draw.rectangle([ |
|
|
x - padding, y - padding, |
|
|
x + text_width + padding, y + text_height + padding |
|
|
], fill=bg_rgb) |
|
|
|
|
|
|
|
|
if text_color.startswith('#'): |
|
|
text_rgb = tuple(int(text_color[i:i+2], 16) for i in (1, 3, 5)) |
|
|
else: |
|
|
text_rgb = (255, 255, 255) |
|
|
|
|
|
|
|
|
draw.text((x, y), new_text, fill=text_rgb, font=font) |
|
|
|
|
|
|
|
|
result_image = np.array(pil_image) |
|
|
|
|
|
|
|
|
if not accessibility_description: |
|
|
accessibility_description = f"Image with text overlay: '{new_text}' positioned at {text_position}" |
|
|
|
|
|
|
|
|
full_accessibility_info = f"Accessibility Description: {accessibility_description}\n\n" |
|
|
if existing_text_info: |
|
|
full_accessibility_info += f"{existing_text_info}\n\n" |
|
|
full_accessibility_info += f"Added text: '{new_text}' at {text_position} with font size {font_size}" |
|
|
|
|
|
return result_image, full_accessibility_info, existing_text_info |
|
|
|
|
|
|
|
|
with gr.Blocks(title="Premium Image Text Overlay - Accessibility Edition") as demo: |
|
|
gr.HTML(""" |
|
|
<div style="text-align: center; margin-bottom: 20px;"> |
|
|
<h1 style="color: #4A90E2; font-size: 2.5em;">🖼️ Premium Image Text Overlay</h1> |
|
|
<p style="font-size: 1.2em; color: #666;">Enhanced accessibility for visually impaired users with text detection</p> |
|
|
<p><a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a></p> |
|
|
</div> |
|
|
""") |
|
|
|
|
|
with gr.Tabs(): |
|
|
with gr.Tab("🖼️ Image Editor"): |
|
|
with gr.Row(): |
|
|
with gr.Column(): |
|
|
input_image = gr.Image( |
|
|
label="Upload Image", |
|
|
type="numpy", |
|
|
height=400, |
|
|
interactive=True |
|
|
) |
|
|
|
|
|
with gr.Accordion("🔍 Text Detection Settings", open=True): |
|
|
detect_existing = gr.Checkbox( |
|
|
label="Detect existing text in image", |
|
|
value=True, |
|
|
info="Automatically detect and display any text already present in the uploaded image" |
|
|
) |
|
|
|
|
|
existing_text_display = gr.Textbox( |
|
|
label="Existing Text Detection Results", |
|
|
interactive=False, |
|
|
max_lines=5, |
|
|
placeholder="Existing text will appear here after detection..." |
|
|
) |
|
|
|
|
|
with gr.Column(): |
|
|
output_image = gr.Image( |
|
|
label="Result Image", |
|
|
type="numpy", |
|
|
height=400, |
|
|
interactive=False |
|
|
) |
|
|
|
|
|
accessibility_output = gr.Textbox( |
|
|
label="♿ Accessibility Information", |
|
|
interactive=False, |
|
|
max_lines=8, |
|
|
placeholder="Comprehensive accessibility description will appear here..." |
|
|
) |
|
|
|
|
|
with gr.Accordion("✏️ Add New Text", open=True): |
|
|
with gr.Row(): |
|
|
new_text = gr.Textbox( |
|
|
label="Text to Add", |
|
|
placeholder="Enter your text here...", |
|
|
max_lines=3 |
|
|
) |
|
|
|
|
|
text_position = gr.Dropdown( |
|
|
choices=[ |
|
|
"top-left", "top-center", "top-right", |
|
|
"center-left", "center", "center-right", |
|
|
"bottom-left", "bottom-center", "bottom-right" |
|
|
], |
|
|
value="bottom-center", |
|
|
label="Text Position" |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
font_size = gr.Slider( |
|
|
minimum=10, |
|
|
maximum=100, |
|
|
value=24, |
|
|
step=2, |
|
|
label="Font Size" |
|
|
) |
|
|
|
|
|
text_color = gr.ColorPicker( |
|
|
value="#FFFFFF", |
|
|
label="Text Color" |
|
|
) |
|
|
|
|
|
background_color = gr.ColorPicker( |
|
|
value="#000000", |
|
|
label="Background Color", |
|
|
info="Set to transparent for no background" |
|
|
) |
|
|
|
|
|
accessibility_description = gr.Textbox( |
|
|
label="Custom Accessibility Description", |
|
|
placeholder="Add a custom description for screen readers (optional)...", |
|
|
max_lines=3 |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
process_btn = gr.Button( |
|
|
"✨ Add Text & Generate Accessibility Info", |
|
|
variant="primary", |
|
|
size="lg" |
|
|
) |
|
|
clear_btn = gr.Button("🗑️ Clear All", variant="secondary") |
|
|
|
|
|
with gr.Tab("ℹ️ How to Use"): |
|
|
gr.Markdown(""" |
|
|
### 🎯 Premium Features for Accessibility |
|
|
|
|
|
**For Visually Impaired Users:** |
|
|
- **Automatic Text Detection**: Our system automatically detects existing text in your uploaded images |
|
|
- **Comprehensive Descriptions**: Get detailed accessibility information including detected text and added overlays |
|
|
- **Screen Reader Friendly**: All outputs are optimized for screen reader compatibility |
|
|
|
|
|
**How It Works:** |
|
|
1. **Upload** your image using the file picker or drag-and-drop |
|
|
2. **Enable** "Detect existing text in image" to automatically find text already present |
|
|
3. **Add** your new text with customizable position, size, and colors |
|
|
4. **Review** the accessibility information that combines detected text and your additions |
|
|
5. **Download** the final image with complete accessibility metadata |
|
|
|
|
|
**Accessibility Best Practices:** |
|
|
- Always provide meaningful custom descriptions when possible |
|
|
- Use high contrast colors for better visibility |
|
|
- Consider the context of existing text when adding new overlays |
|
|
- The accessibility information is designed to be read by screen readers |
|
|
""") |
|
|
|
|
|
|
|
|
process_btn.click( |
|
|
fn=add_text_to_image, |
|
|
inputs=[ |
|
|
input_image, |
|
|
new_text, |
|
|
text_position, |
|
|
font_size, |
|
|
text_color, |
|
|
background_color, |
|
|
detect_existing, |
|
|
accessibility_description |
|
|
], |
|
|
outputs=[ |
|
|
output_image, |
|
|
accessibility_output, |
|
|
existing_text_display |
|
|
] |
|
|
) |
|
|
|
|
|
clear_btn.click( |
|
|
fn=lambda: (None, "", "", ""), |
|
|
inputs=[], |
|
|
outputs=[ |
|
|
input_image, |
|
|
output_image, |
|
|
accessibility_output, |
|
|
existing_text_display |
|
|
] |
|
|
) |
|
|
|
|
|
|
|
|
input_image.change( |
|
|
fn=lambda img, detect: (detect_text_in_image(img) if detect and img is not None else []) if detect else [], |
|
|
inputs=[input_image, detect_existing], |
|
|
outputs=existing_text_display |
|
|
) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch() |