Spaces:
Running
Running
| from fastapi import FastAPI, File, UploadFile, Form | |
| from fastapi.responses import HTMLResponse | |
| from fastapi.staticfiles import StaticFiles | |
| from PIL import Image | |
| import requests | |
| from io import BytesIO | |
| import base64 | |
| import os | |
| from tkinter import Tk, Label, Button, Radiobutton, IntVar | |
| app = FastAPI() | |
| # Mount static folder | |
| app.mount("/static", StaticFiles(directory="static"), name="static") | |
| # Function to crop to desired dimensions while keeping ratio | |
| from PIL import Image | |
| def cropper(img: Image.Image, target_width: int, target_height: int) -> Image.Image: | |
| # Clamp target dimensions to a maximum of 1600px | |
| target_width = min(target_width, 1600) | |
| target_height = min(target_height, 1600) | |
| # Original size | |
| orig_w, orig_h = img.size | |
| target_ratio = target_width / target_height | |
| orig_ratio = orig_w / orig_h | |
| # Scale image to cover target box | |
| if orig_ratio > target_ratio: | |
| # Image is wider than target → scale by height | |
| new_h = target_height | |
| new_w = int(orig_w * (target_height / orig_h)) | |
| else: | |
| # Image is taller/narrower → scale by width | |
| new_w = target_width | |
| new_h = int(orig_h * (target_width / orig_w)) | |
| img_resized = img.resize((new_w, new_h), Image.LANCZOS) | |
| # Crop center | |
| left = max((new_w - target_width) // 2, 0) | |
| top = max((new_h - target_height) // 2, 0) | |
| right = left + target_width | |
| bottom = top + target_height | |
| return img_resized.crop((left, top, right, bottom)) | |
| # Home Page | |
| def home_page(): | |
| return """ | |
| <html> | |
| <head> | |
| <title>Part of Idoia's Developer Portfolio - Innovating the Web</title> | |
| <link rel="stylesheet" href="/static/styles/style.css"> | |
| <!-- Meta Tags for SEO --> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <meta name="description" content="Explore the developer portfolio of Idoia, showcasing expertise in FastAPI, web development, and cutting-edge applications."> | |
| <meta name="keywords" content="Idoia, Developer, FastAPI, Web Development, Python Projects, Image Processing, Online Portfolio"> | |
| <meta name="author" content="Idoia"> | |
| <!-- Open Graph Meta Tags --> | |
| <meta property="og:title" content="Idoia's Developer Portfolio - Innovating the Web"> | |
| <meta property="og:description" content="Showcasing FastAPI projects, web apps, and image processing expertise. Explore Idoia's developer journey."> | |
| <meta property="og:image" content="/static/images/banner.jpg"> | |
| <meta property="og:url" content="https://webdevserv.github.io/html_bites/dev/webdev.html"> | |
| <meta property="og:type" content="website"> | |
| <!-- Twitter Card Meta Tags --> | |
| <meta name="twitter:card" content="summary_large_image"> | |
| <meta name="twitter:title" content="Idoia's Developer Portfolio - Innovating the Web"> | |
| <meta name="twitter:description" content="Discover the developer profile of Idoia. Dive into FastAPI-powered web apps and creative Python projects."> | |
| <meta name="twitter:image" content="/static/images/banner.jpg"> | |
| <link rel="icon" href="/static/images/6464.ico" type="image/x-icon"> | |
| <!-- Google Fonts (Optional for Styling) --> | |
| <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet"> | |
| <!-- Schema.org JSON-LD (Optional for Rich Snippets) --> | |
| <script type="application/ld+json"> | |
| { | |
| "@context": "https://schema.org", | |
| "@type": "Person", | |
| "name": "Idoia", | |
| "jobTitle": "Web Developer", | |
| "url": "https://webdevserv.github.io/html_bites/dev/webdev.html", | |
| "image": "https://idoia-dev-portfolio.com/static/images/banner.jpg", | |
| "description": "Experienced web developer with a focus on Streamlit, HF, Python, and modern web applications." | |
| } | |
| </script> | |
| </head> | |
| <body> | |
| <img class="banner" src="/static/images/banner.jpg" alt="Banner" width="100%"> | |
| <h2>Recsize Image App</h2> | |
| <p>Please select an option below:</p> | |
| <ul> | |
| <li><a href="/demo">Demo</a></li> | |
| <li><a href="/application">Application</a></li> | |
| </ul> | |
| <div id="credit">Image credit | |
| <a href="https://stock.adobe.com/es/contributor/212598146/UMAMI%20LAB" target="_blank">Adobe Stock User Umami Lab</a> | |
| and | |
| <a href="https://www.shutterstock.com/g/Idoia+Lerchundi?rid=430751957" target="_blank">Shutterstock User PhoArt101</a>. | |
| </div> | |
| </body> | |
| </html> | |
| """ | |
| # Demo Page | |
| def demo_page(): | |
| # URLs for demo images | |
| url1 = "https://raw.githubusercontent.com/webdevserv/images_video/main/squareportrait.png" | |
| url2 = "https://raw.githubusercontent.com/webdevserv/images_video/main/squarelandscape.png" | |
| # Process the first image | |
| response = requests.get(url1) | |
| img1 = Image.open(BytesIO(response.content)).convert("RGB") | |
| rectangled_img1 = cropper(img1,200,200) | |
| output1 = BytesIO() | |
| rectangled_img1.save(output1, format="JPEG") | |
| encoded_img1 = base64.b64encode(output1.getvalue()).decode("utf-8") | |
| # Process the second image | |
| response = requests.get(url2) | |
| img2 = Image.open(BytesIO(response.content)).convert("RGB") | |
| rectangled_img2 = cropper(img2,300,300) | |
| output2 = BytesIO() | |
| rectangled_img2.save(output2, format="JPEG") | |
| encoded_img2 = base64.b64encode(output2.getvalue()).decode("utf-8") | |
| return f""" | |
| <html> | |
| <head> | |
| <title>Part of Idoia's Developer Portfolio - Innovating the Web</title> | |
| <link rel="stylesheet" href="/static/styles/style.css"> | |
| </head> | |
| <body> | |
| <img class="banner" src="/static/images/banner.jpg" alt="Banner" width="100%"> | |
| <h2>Recsize Image Demo (CPU Optimized)</h2> | |
| <p>Image will be resized as per your input pixel numbers.</p> | |
| <h3>Result 1:</h3> | |
| <img src="data:image/jpeg;base64,{encoded_img1}" /> | |
| <h3>Result 2:</h3> | |
| <img src="data:image/jpeg;base64,{encoded_img2}" /> | |
| <p><a href="/">Back</a></p> | |
| <div id="credit">Image credit | |
| <a href="https://stock.adobe.com/es/contributor/212598146/UMAMI%20LAB" target="_blank">Adobe Stock User Umami Lab</a> | |
| and | |
| <a href="https://www.shutterstock.com/g/Idoia+Lerchundi?rid=430751957" target="_blank">Shutterstock User PhoArt101</a>. | |
| </div> | |
| </body> | |
| </html> | |
| """ | |
| def application_page(): | |
| return """ | |
| <html> | |
| <head> | |
| <title>Part of Idoia's Developer Portfolio - Innovating the Web</title> | |
| <link rel="stylesheet" href="/static/styles/style.css"> | |
| </head> | |
| <body> | |
| <img class="banner" src="/static/images/banner.jpg" alt="Banner" width="100%"> | |
| <h2>Resize Image Application (CPU Optimized)</h2> | |
| <p>Upload a JPG image and choose your target dimensions. Max 1600px per side.</p> | |
| <form action="/upload/" enctype="multipart/form-data" method="post"> | |
| <label for="file">Upload your image:</label> | |
| <input name="file" type="file" required><br><br> | |
| <label for="target_width">Target Width (px):</label> | |
| <input type="number" id="target_width" name="target_width" min="1" max="1600" required><br><br> | |
| <label for="target_height">Target Height (px):</label> | |
| <input type="number" id="target_height" name="target_height" min="1" max="1600" required><br><br> | |
| <input type="submit" value="Resize It"> | |
| </form> | |
| <a href="/">Back</a> | |
| <div id="credit">Image credit | |
| <a href="https://stock.adobe.com/es/contributor/212598146/UMAMI%20LAB" target="_blank">Adobe Stock User Umami Lab</a> | |
| and | |
| <a href="https://www.shutterstock.com/g/Idoia+Lerchundi?rid=430751957" target="_blank">Shutterstock User PhoArt101</a>. | |
| </div> | |
| </body> | |
| </html> | |
| """ | |
| async def upload_file( | |
| file: UploadFile = File(...), | |
| target_width: int = Form(...), | |
| target_height: int = Form(...) | |
| ): | |
| try: | |
| # Await file upload | |
| contents = await file.read() | |
| img = Image.open(BytesIO(contents)).convert("RGB") | |
| # Crop to the user’s chosen dimensions (cropper clamps to max 1024px) | |
| cropped_img = cropper(img, target_width, target_height) | |
| # Save the cropped image (original size) | |
| output = BytesIO() | |
| cropped_img.save(output, format="JPEG") | |
| output.seek(0) | |
| # Encode the full-size image for download | |
| full_size_encoded_img = base64.b64encode(output.getvalue()).decode("utf-8") | |
| # Resize the image for display (512px wide, keep aspect ratio) | |
| display_img = cropped_img.copy() | |
| desired_width = 512 | |
| aspect_ratio = display_img.height / display_img.width | |
| desired_height = int(desired_width * aspect_ratio) | |
| display_img.thumbnail((desired_width, desired_height)) | |
| display_output = BytesIO() | |
| display_img.save(display_output, format="JPEG") | |
| display_output.seek(0) | |
| # Encode the resized display image | |
| display_encoded_img = base64.b64encode(display_output.getvalue()).decode("utf-8") | |
| # Return the HTML response | |
| return HTMLResponse( | |
| content=f""" | |
| <html> | |
| <head> | |
| <title>Part of Idoia's Developer Portfolio - Innovating the Web</title> | |
| <link rel="stylesheet" href="/static/styles/style.css"> | |
| </head> | |
| <body> | |
| <img class="banner" src="/static/images/banner.jpg" alt="Banner" width="100%"> | |
| <h2>Image successfully cropped!</h2> | |
| <img src='data:image/jpeg;base64,{display_encoded_img}' width="512"/> | |
| <p><a href="data:image/jpeg;base64,{full_size_encoded_img}" download="cropped_image.jpg"> | |
| Download Full-Size Image</a></p> | |
| <p><a href="/">Back</a></p> | |
| </body> | |
| </html> | |
| """, | |
| media_type="text/html" | |
| ) | |
| except Exception as e: | |
| return HTMLResponse(content=f"<h3>An error occurred: {e}</h3>", media_type="text/html") | |
| if __name__ == "__main__": | |
| import uvicorn, os | |
| uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 7860))) | |