version initiale
This commit is contained in:
commit
7b2e43c5f7
13 changed files with 278 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/.idea/
|
18
Dockerfile
Normal file
18
Dockerfile
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Use an official Python runtime as a parent image
|
||||
FROM python:3.9-slim-buster
|
||||
|
||||
# Set the working directory to /app
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the requirements file into the container and install dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy the rest of the application code into the container
|
||||
COPY main.py .
|
||||
|
||||
# Expose the port that the application will listen on
|
||||
EXPOSE 8088
|
||||
|
||||
# Start the FastAPI application
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8088"]
|
BIN
backgrounds/fonds_slide.png
Normal file
BIN
backgrounds/fonds_slide.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
11
build-local.sh
Normal file
11
build-local.sh
Normal file
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Build the Docker image with the "local/centrer-image-backend" tag
|
||||
docker build -t local/centrer-image-backend .
|
||||
|
||||
# Check if the Docker image was built successfully
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Docker image built successfully."
|
||||
else
|
||||
echo "Error: Docker image build failed."
|
||||
fi
|
9
centrer-image-backend.iml
Normal file
9
centrer-image-backend.iml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
28
docker-compose.yml
Normal file
28
docker-compose.yml
Normal file
|
@ -0,0 +1,28 @@
|
|||
version: '3'
|
||||
|
||||
services:
|
||||
centrer-image-backend:
|
||||
image: local/centrer-image-backend
|
||||
ports:
|
||||
- "8088:8088"
|
||||
networks:
|
||||
- centrer-image
|
||||
volumes:
|
||||
- ./backgrounds:/app/backgrounds
|
||||
- ./images:/app/images
|
||||
- ./mosaics:/app/mosaics
|
||||
|
||||
centrer-image-frontend:
|
||||
image: local/centrer-image-frontend
|
||||
ports:
|
||||
- "8501:8501"
|
||||
networks:
|
||||
- centrer-image
|
||||
depends_on:
|
||||
- centrer-image-backend
|
||||
environment:
|
||||
BACKEND: 'http://centrer-image-backend:8088'
|
||||
|
||||
networks:
|
||||
centrer-image:
|
||||
driver: bridge
|
3
images/images.md
Normal file
3
images/images.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Images
|
||||
|
||||
Ce répertoire contient des images
|
1
install.sh
Normal file
1
install.sh
Normal file
|
@ -0,0 +1 @@
|
|||
pip install -r requirements.txt
|
144
main.py
Normal file
144
main.py
Normal file
|
@ -0,0 +1,144 @@
|
|||
import datetime
|
||||
|
||||
from fastapi import FastAPI, File, UploadFile
|
||||
from PIL import Image
|
||||
import os
|
||||
import io
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
import random
|
||||
import glob
|
||||
import hashlib
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
BACKGROUND_DIR = 'backgrounds'
|
||||
IMAGE_DIR = 'images'
|
||||
IMAGE_EXT = '.png'
|
||||
|
||||
|
||||
class MosaicRequest(BaseModel):
|
||||
rows: int
|
||||
columns: int
|
||||
buffer_size: int
|
||||
keywords: List[str]
|
||||
background_file: str
|
||||
|
||||
class BackgroundRequest(BaseModel):
|
||||
backgrounds: List[str]
|
||||
|
||||
|
||||
def get_images():
|
||||
image_files = [f for f in os.listdir(IMAGE_DIR) if f.endswith(IMAGE_EXT)]
|
||||
image_files.sort()
|
||||
return [Image.open(os.path.join(IMAGE_DIR, f)) for f in image_files]
|
||||
|
||||
|
||||
def create_mosaic(images, rows, cols, buffer_size):
|
||||
mosaic_width = cols * images[0].size[0] + (cols - 1) * buffer_size
|
||||
mosaic_height = rows * images[0].size[1] + (rows - 1) * buffer_size
|
||||
mosaic = Image.new('RGBA', (mosaic_width, mosaic_height), color=(0, 0, 0, 0))
|
||||
for i, img in enumerate(images):
|
||||
row = i // cols
|
||||
col = i % cols
|
||||
offset_x = (images[0].size[0] - img.size[0]) // 2
|
||||
offset_y = (images[0].size[1] - img.size[1]) // 2
|
||||
pos_x = col * (img.size[0] + buffer_size)
|
||||
pos_y = row * (img.size[1] + buffer_size)
|
||||
pos = (pos_x + offset_x, pos_y + offset_y)
|
||||
mosaic.paste(img, pos)
|
||||
return mosaic
|
||||
|
||||
|
||||
def place_image_on_background(foreground: Image.Image, background: Image.Image) -> Image.Image:
|
||||
# Compute the position to paste the foreground image
|
||||
x = (background.width - foreground.width) // 2
|
||||
y = (background.height - foreground.height) // 2
|
||||
|
||||
# Create a new image with the background and paste the foreground onto it
|
||||
result = Image.new("RGBA", (background.width, background.height), (255, 255, 255, 0))
|
||||
result.paste(background, (0, 0))
|
||||
result.alpha_composite(foreground, (x, y))
|
||||
|
||||
return result
|
||||
|
||||
@app.get("/backgrounds/")
|
||||
async def list_backgrounds():
|
||||
background_files = [f for f in os.listdir(BACKGROUND_DIR) if f.endswith(IMAGE_EXT)]
|
||||
background_files.sort()
|
||||
background_dict = {"backgrounds": background_files}
|
||||
return BackgroundRequest(**background_dict)
|
||||
|
||||
@app.post("/upload_file/")
|
||||
async def upload_file(file: UploadFile = File(...), directory=IMAGE_DIR):
|
||||
# check if the file is an image file
|
||||
if not file.content_type.startswith('image'):
|
||||
raise ValueError('File is not an image')
|
||||
|
||||
# read the file data and create a Pillow Image object
|
||||
image = Image.open(file.file)
|
||||
|
||||
# save the PNG image to the "images/" directory
|
||||
filename = os.path.join(directory, os.path.splitext(file.filename)[0] + '.png')
|
||||
image.save(filename)
|
||||
|
||||
return {"filename": filename}
|
||||
|
||||
|
||||
@app.post("/upload_background")
|
||||
async def upload_background(file: UploadFile = File(...)):
|
||||
filename_d = await upload_file(file, directory=BACKGROUND_DIR)
|
||||
return filename_d
|
||||
|
||||
|
||||
@app.post("/generate_mosaic")
|
||||
async def generate_mosaic(request: MosaicRequest):
|
||||
images = []
|
||||
for keyword in request.keywords:
|
||||
file_list = glob.glob(f'images/{keyword}*.png')
|
||||
if not file_list:
|
||||
return {"message": f"No images found for keyword: {keyword}"}
|
||||
selected_file = random.choice(file_list)
|
||||
with open(selected_file, "rb") as f:
|
||||
images.append(Image.open(io.BytesIO(f.read())))
|
||||
|
||||
# select background image
|
||||
background = Image.open(f"backgrounds/{request.background_file}")
|
||||
|
||||
# resize images to have the same height
|
||||
height = min(background.height - (request.rows + 1) * request.buffer_size,
|
||||
min(img.size[1] for img in images)) / request.rows
|
||||
images = [img.resize((round(img.size[0]*height/img.size[1]),
|
||||
round(height))) for img in images]
|
||||
|
||||
# create grid
|
||||
grid = create_mosaic(images, request.rows, request.columns, request.buffer_size)
|
||||
|
||||
# select background image
|
||||
background = Image.open(f"backgrounds/{request.background_file}")
|
||||
|
||||
# place grid on background
|
||||
final_image = place_image_on_background(grid, background)
|
||||
|
||||
# convert image to bytes
|
||||
img_bytes = final_image.tobytes()
|
||||
|
||||
# create MD5 hash object
|
||||
hash_obj = hashlib.md5()
|
||||
|
||||
# update hash object with image bytes
|
||||
hash_obj.update(img_bytes)
|
||||
|
||||
# get hex representation of hash
|
||||
hash_hex = hash_obj.hexdigest()
|
||||
|
||||
# convert hex to string
|
||||
hash_str = str(hash_hex)
|
||||
|
||||
# save final image
|
||||
now = datetime.datetime.now()
|
||||
filename_datetime = now.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
|
||||
final_image.save(f"mosaics/{hash_str}-{filename_datetime}.png")
|
||||
|
||||
return {"message": "Mosaic image created successfully"}
|
3
mosaics/mosaic.md
Normal file
3
mosaics/mosaic.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Images
|
||||
|
||||
Ce répertoire contient des mosaiques générées
|
6
requirements.txt
Normal file
6
requirements.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
fastapi==0.95.1
|
||||
uvicorn==0.22.0
|
||||
Pillow==9.5.0
|
||||
python-multipart==0.0.6
|
||||
pydantic~=1.10.7
|
||||
requests~=2.30.0
|
4
run.sh
Normal file
4
run.sh
Normal file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
docker-compose down
|
||||
docker-compose up
|
50
test.http
Normal file
50
test.http
Normal file
|
@ -0,0 +1,50 @@
|
|||
POST http://localhost:8088/generate_mosaic HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"rows": 1,
|
||||
"columns": 1,
|
||||
"buffer_size": 150,
|
||||
"background_file": "fonds_slide.png",
|
||||
"keywords": ["unicorn"]
|
||||
}
|
||||
|
||||
################################
|
||||
|
||||
POST http://localhost:8088/generate_mosaic HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"rows": 1,
|
||||
"columns": 2,
|
||||
"buffer_size": 150,
|
||||
"background_file": "fonds_slide.png",
|
||||
"keywords": ["unicorn","thief"]
|
||||
}
|
||||
|
||||
################################
|
||||
|
||||
POST http://localhost:8088/generate_mosaic HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"rows": 2,
|
||||
"columns": 2,
|
||||
"buffer_size": 150,
|
||||
"background_file": "fonds_slide.png",
|
||||
"keywords": ["thief","unicorn","unicorn","thief"]
|
||||
}
|
||||
|
||||
|
||||
################################
|
||||
|
||||
POST http://localhost:8088/generate_mosaic HTTP/1.1
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"rows": 10,
|
||||
"columns": 10,
|
||||
"buffer_size": 100,
|
||||
"keywords": ["cat", "dog"],
|
||||
"background_file": "fonds_slide.png"
|
||||
}
|
Loading…
Reference in a new issue