| from fastapi import FastAPI, File, UploadFile |
| from fastapi.responses import HTMLResponse |
| from fastapi.staticfiles import StaticFiles |
| import numpy as np |
| from PIL import Image |
| from io import BytesIO |
| import requests |
| import base64 |
| import os |
|
|
| app = FastAPI() |
|
|
| |
| app.mount("/static", StaticFiles(directory="static"), name="static") |
|
|
| |
| def fill_square_cropper(img): |
| imgsz = [img.height, img.width] |
| avg_color_per_row = np.average(img, axis=0) |
| avg_color = np.average(avg_color_per_row, axis=0) |
|
|
| if img.height > img.width: |
| newimg = Image.new( |
| 'RGB', |
| (img.height, img.height), |
| (round(avg_color[0]), round(avg_color[1]), round(avg_color[2])) |
| ) |
| newpos = (img.height - img.width) // 2 |
| newimg.paste(img, (newpos, 0)) |
| return newimg |
|
|
| elif img.width > img.height: |
| newimg = Image.new( |
| 'RGB', |
| (img.width, img.width), |
| (round(avg_color[0]), round(avg_color[1]), round(avg_color[2])) |
| ) |
| newpos = (img.width - img.height) // 2 |
| newimg.paste(img, (0, newpos)) |
| return newimg |
| else: |
| return img |
|
|
| |
| @app.get("/", response_class=HTMLResponse) |
| 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"> |
| <link rel="stylesheet" href="/static/styles/w3.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>Square and Fill 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> |
| """ |
|
|
| |
| @app.get("/demo", response_class=HTMLResponse) |
| def demo_page(): |
| |
| url1 = "https://raw.githubusercontent.com/webdevserv/images_video/main/cowportrait.jpg" |
| url2 = "https://raw.githubusercontent.com/webdevserv/images_video/main/cowlandscape.jpg" |
|
|
| |
| response = requests.get(url1) |
| img1 = Image.open(BytesIO(response.content)).convert("RGB") |
| squared_img1 = fill_square_cropper(img1) |
| output1 = BytesIO() |
| squared_img1.save(output1, format="JPEG") |
| encoded_img1 = base64.b64encode(output1.getvalue()).decode("utf-8") |
|
|
| |
| response = requests.get(url2) |
| img2 = Image.open(BytesIO(response.content)).convert("RGB") |
| squared_img2 = fill_square_cropper(img2) |
| output2 = BytesIO() |
| squared_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"> |
| <link rel="stylesheet" href="/static/styles/w3.css"> |
| </head> |
| <body> |
| <img class="banner" src="/static/images/banner.jpg" alt="Banner" width="100%"> |
| <h2>Square Image Demo</h2> |
| <p>Image will be squared with color filler where applicable.</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> |
| """ |
|
|
| |
| @app.get("/application", response_class=HTMLResponse) |
| 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"> |
| <link rel="stylesheet" href="/static/styles/w3.css"> |
| </head> |
| <body> |
| <img class="banner" src="/static/images/banner.jpg" alt="Banner" width="100%"> |
| <h2>Square Image Application</h2> |
| <p>Upload a JPG or PNG image to square and fill with color padding.</p> |
| <form action="/upload/" enctype="multipart/form-data" method="post"> |
| <input name="file" type="file"> |
| <input type="submit" value="Square 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> |
| """ |
|
|
| @app.post("/upload/") |
| async def upload_file(file: UploadFile = File(...)): |
| try: |
| |
| contents = await file.read() |
| img = Image.open(BytesIO(contents)).convert("RGB") |
| squared_img = fill_square_cropper(img) |
|
|
| |
| output = BytesIO() |
| squared_img.save(output, format="JPEG") |
| output.seek(0) |
|
|
| |
| full_size_encoded_img = base64.b64encode(output.getvalue()).decode("utf-8") |
|
|
| |
| display_img = squared_img.copy() |
| display_img.thumbnail((512, 512)) |
| display_output = BytesIO() |
| display_img.save(display_output, format="JPEG") |
| display_output.seek(0) |
|
|
| |
| display_encoded_img = base64.b64encode(display_output.getvalue()).decode("utf-8") |
|
|
| |
| 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"> |
| <link rel="stylesheet" href="/static/styles/w3.css"> |
| </head> |
| <body> |
| <img class="banner" src="/static/images/banner.jpg" alt="Banner" width="100%"> |
| <h2>Image successfully squared!</h2> |
| <img src='data:image/jpeg;base64,{display_encoded_img}' width="512" height="512" /> |
| <p><a href="data:image/jpeg;base64,{full_size_encoded_img}" download="squared_image.jpg"> |
| Download Full-Size Image</a></p> |
| <div style="margin: 0.75em 0;"><a href="https://www.buymeacoffee.com/Artgen" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174"></a></div> |
| <div style="margin: 0.75em 0;">But what would really help me is a <strong>PRO subscription</strong> to Google Colab, Kaggle or Hugging Face. Many thanks.</div> |
| <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> |
| """, |
| 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 |
| uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 7860))) |