# HG changeset patch # User Goffi # Date 1597355178 -7200 # Node ID 1512cbd6c4ac690ed36591ebe1419eb704b72dd7 # Parent b1e9f17fbb5af600206e557093128da706b8321f tools (image): fix_orientation on resize + `fix_orientation` method: a new argument (`True` by default) fix orientation (i.e. rotate according to EXIF data) of image when resizing it. A new `fix_orientation` method (unused for now) let do it independently diff -r b1e9f17fbb5a -r 1512cbd6c4ac sat/tools/image.py --- a/sat/tools/image.py Thu Aug 13 23:46:18 2020 +0200 +++ b/sat/tools/image.py Thu Aug 13 23:46:18 2020 +0200 @@ -20,7 +20,7 @@ import tempfile import mimetypes -from PIL import Image +from PIL import Image, ImageOps from pathlib import Path from twisted.internet import threads from sat.core.i18n import _ @@ -69,10 +69,12 @@ return report -def _resizeBlocking(image_path, new_size, dest): +def _resize_blocking(image_path, new_size, dest, fix_orientation): im_path = Path(image_path) im = Image.open(im_path) resized = im.resize(new_size, Image.LANCZOS) + if fix_orientation: + resized = ImageOps.fix_orientation(resized) if dest is None: dest = tempfile.NamedTemporaryFile(suffix=im_path.suffix, delete=False) @@ -85,7 +87,7 @@ return Path(f.name) -def resize(image_path, new_size, dest=None): +def resize(image_path, new_size, dest=None, fix_orientation=True): """Resize an image to a new file, and return its path @param image_path(str, Path): path of the original image @@ -95,13 +97,15 @@ file will be converted to PNG - Path: path to the file to create/overwrite - file: a file object which must be opened for writing in binary mode + @param fix_orientation: if True, use EXIF data to set orientation @return (Path): path of the resized file. The image at this path should be deleted after use """ - return threads.deferToThread(_resizeBlocking, image_path, new_size, dest) + return threads.deferToThread( + _resize_blocking, image_path, new_size, dest, fix_orientation) -def _convertBlocking(image_path, dest, extra): +def _convert_blocking(image_path, dest, extra): media_type = mimetypes.guess_type(str(image_path), strict=False)[0] if dest is None: @@ -173,7 +177,30 @@ raise ValueError(f"There is no file at {image_path}!") if extra is None: extra = {} - return threads.deferToThread(_convertBlocking, image_path, dest, extra) + return threads.deferToThread(_convert_blocking, image_path, dest, extra) + + +def __fix_orientation_blocking(image_path): + im = Image.open(image_path) + im_format = im.format + exif = im.getexif() + orientation = exif.get(0x0112) + if orientation is None or orientation<2: + # nothing to do + return False + im = ImageOps.exif_transpose(im) + im.save(image_path, im_format) + log.debug(f"image {image_path} orientation has been fixed") + return True + + +def fix_orientation(image_path: Path) -> bool: + """Apply orientation found in EXIF data if any + + @param image_path: image location, image will be modified in place + @return True if image has been modified + """ + return threads.deferToThread(__fix_orientation_blocking, image_path) def guess_type(source):