Pridani editace fotek

This commit is contained in:
Michal Kváček
2023-10-27 22:30:59 +02:00
parent 322e61bce3
commit 3223d2ac00
17 changed files with 602 additions and 54 deletions
+189 -4
View File
@@ -1,7 +1,11 @@
import io
import math
from datetime import datetime
from typing import Optional
from typing import Optional, Tuple
import exif
from PIL import Image, UnidentifiedImageError
from PIL.ImageEnhance import Brightness, Contrast, Color, Sharpness
from utils.file import check_directories
from utils.gps import gps_to_decimal
@@ -27,6 +31,114 @@ async def parse_exif_info(path: str, filename: str) -> dict:
return exif_info
class PhotoEditor:
def __init__(self, path: str, filename: str):
self.path = path
self.filename = filename
self.img = Image.open(f"{path}/{filename}")
self.img_size = self.img.size
def resize(self, new_width: Optional[int] = None, new_height: Optional[int] = None):
if not new_width and not new_height:
raise ValueError("Set either new_width or new_height")
w, h = self.img.size
aspect_ratio = w / h
if not new_width:
# TODO: tohle bude asi blbe, tu se bude muset asi delit?
new_width = int(new_height * aspect_ratio)
if not new_height:
new_height = int(new_width / aspect_ratio)
self.img = self.img.resize((new_width, new_height), Image.BICUBIC)
self.img_size = self.img.size
return self
def _get_cropbox_after_rotate(self, degrees: float, rotated_width: int, rotated_height: int) -> Tuple[int, int]:
original_width, original_height = self.img_size
aspect_ratio = float(original_width) / original_height
rotated_aspect_ratio = float(rotated_width) / rotated_height
angle = math.fabs(degrees) * math.pi / 180
if aspect_ratio < 1:
total_height = float(original_width) / rotated_aspect_ratio
else:
total_height = float(original_height)
h = total_height / (aspect_ratio * math.sin(angle) + math.cos(angle))
w = h * aspect_ratio
return round(w), round(h)
def rotate(self, degrees: float, crop_after_rotate: bool = False):
self.img = self.img.rotate(degrees, resample=Image.Resampling.BICUBIC, expand=True)
if crop_after_rotate:
rotated_width, rotated_height = self.img.size
new_width, new_height = self._get_cropbox_after_rotate(degrees, rotated_width, rotated_height)
left = round((rotated_width - new_width) / 2)
top = round((rotated_height - new_height) / 2)
self.img = self.img.crop((left, top, new_width, new_height))
self.img_size = self.img.size
return self
def crop(self, left: float, top: float, width: float, height: float):
w, h = self.img_size
left_px = int(left * w)
top_px = int(top * h)
width_px = int(width * w)
height_px = int(height * h)
self.img = self.img.crop((left_px, top_px, left_px + width_px, top_px + height_px))
self.img_size = self.img.size
return self
def adjust(
self,
brightness: Optional[float] = None,
contrast: Optional[float] = None,
saturation: Optional[float] = None,
sharpness: Optional[float] = None
):
adjustments = [
(Brightness, brightness),
(Contrast, contrast),
(Color, saturation),
(Sharpness, sharpness)
]
for adjustment, value in adjustments:
if value is not None:
self.img = adjustment(self.img).enhance(value)
return self
def get_as_stream(self):
img_io = io.BytesIO()
self.img.save(img_io, 'JPEG')
img_io.seek(0)
return img_io
def write_to_file(
self, quality: int = 90, dest_path: Optional[str] = None, dest_filename: Optional[str] = None
) -> str:
dest = f"{dest_path or self.path}/{dest_filename or self.filename}"
self.img.save(dest, 'JPEG', quality=quality)
return dest
# -----------
# odsud niz to bude vse asi na smazani, vse by mela umet trida PhotoEditor
async def resize_image(
path: str, filename: str, new_width: int, quality: int = 90,
dest_path: Optional[str] = None,
@@ -47,14 +159,34 @@ async def resize_image(
image = image.resize((new_width, new_height), Image.LANCZOS)
check_directories(dest_path)
image.save(f"{dest_path}/{dest_filename}", 'JPEG', quality=quality)
return new_width, new_height
except UnidentifiedImageError:
pass
async def rotate_image(
def crop_image_after_rotate(
original_width: int, original_height: int, rotated_width: int, rotated_height: int, degrees: float
) -> Tuple[int, int]:
aspect_ratio = float(original_width) / original_height
rotated_aspect_ratio = float(rotated_width) / rotated_height
angle = math.fabs(degrees) * math.pi / 180
if aspect_ratio < 1:
total_height = float(original_width) / rotated_aspect_ratio
else:
total_height = float(original_height)
h = total_height / (aspect_ratio * math.sin(angle) + math.cos(angle))
w = h * aspect_ratio
return round(w), round(h)
async def rotate_image_no_crop(
path: str,
filename: str,
angle: int,
angle: float,
dest_path: Optional[str] = None,
dest_filename: Optional[str] = None
):
@@ -65,6 +197,59 @@ async def rotate_image(
dest_filename = filename
img = Image.open(f"{path}/{filename}")
img = img.rotate(angle, Image.LANCZOS, expand=True)
rotated_img = img.rotate(angle, Image.BICUBIC, expand=True)
check_directories(dest_path)
rotated_img.save(f"{dest_path}/{dest_filename}", 'JPEG', quality=100)
async def adjust_image(
path: str,
filename: str,
rotate: float,
brightness: float,
contrast: float,
saturation: float,
sharpness: float,
crop: dict,
dest_path: Optional[str] = None,
dest_filename: Optional[str] = None
):
if not dest_path:
dest_path = path
if not dest_filename:
dest_filename = filename
img = Image.open(f"{path}/{filename}")
if rotate:
rotated_img = img.rotate(rotate, Image.BICUBIC, expand=True)
width, height = img.size
rotated_width, rotated_height = rotated_img.size
new_width, new_height = crop_image_after_rotate(width, height, rotated_width, rotated_height, rotate)
left = round((rotated_width - new_width) / 2)
top = round((rotated_height - new_height) / 2)
img = rotated_img.crop((left, top, left + new_width, top + new_height))
adjustments = [
(Brightness, brightness),
(Contrast, contrast),
(Color, saturation),
(Sharpness, sharpness)
]
for adj, value in adjustments:
img = adj(img).enhance(value)
if crop:
w, h = img.size
left = crop['left'] * w
top = crop['top'] * h
width = crop['width'] * w
height = crop['height'] * h
img = img.crop((left, top, left + width, top + height))
img.save(f"{dest_path}/{dest_filename}", 'JPEG', quality=100)