Mercurial > libervia-backend
diff sat/tools/image.py @ 3259:f300d78f08f3
core: image convertion + SVG support:
/!\ new optional dependency: CairoSVG (with installed `[SVG]` extra)
- new `convert` method in `tools.image` to save an image in an other format, with support
for SVG (when CairoSVG is available)
- new `imageConvert` method is available for frontends
author | Goffi <goffi@goffi.org> |
---|---|
date | Sun, 19 Apr 2020 16:53:44 +0200 |
parents | 54934ee3f69c |
children | 1512cbd6c4ac |
line wrap: on
line diff
--- a/sat/tools/image.py Sun Apr 19 16:40:34 2020 +0200 +++ b/sat/tools/image.py Sun Apr 19 16:53:44 2020 +0200 @@ -23,6 +23,18 @@ from PIL import Image from pathlib import Path from twisted.internet import threads +from sat.core.i18n import _ +from sat.core import exceptions +from sat.core.log import getLogger + +log = getLogger(__name__) + +try: + import cairosvg +except Exception as e: + log.warning(_("SVG support not available, please install cairosvg: {e}").format( + e=e)) + cairosvg = None def check(host, path, max_size=None): @@ -57,7 +69,7 @@ return report -def _resizeBlocking(image_path, new_size, dest=None): +def _resizeBlocking(image_path, new_size, dest): im_path = Path(image_path) im = Image.open(im_path) resized = im.resize(new_size, Image.LANCZOS) @@ -80,6 +92,7 @@ @param new_size(tuple[int, int]): size to use for new image @param dest(None, Path, file): where the resized image must be stored, can be: - None: use a temporary file + 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 @return (Path): path of the resized file. @@ -88,6 +101,81 @@ return threads.deferToThread(_resizeBlocking, image_path, new_size, dest) +def _convertBlocking(image_path, dest, extra): + media_type = mimetypes.guess_type(str(image_path), strict=False)[0] + + if dest is None: + dest = tempfile.NamedTemporaryFile(suffix=".png", delete=False) + filepath = Path(dest.name) + elif isinstance(dest, Path): + filepath = dest + else: + # we should have a file-like object + try: + name = dest.name + except AttributeError: + name = None + if name: + try: + filepath = Path(name) + except TypeError: + filepath = Path('noname.png') + else: + filepath = Path('noname.png') + + if media_type == "image/svg+xml": + if cairosvg is None: + raise exceptions.MissingModule( + f"Can't convert SVG image at {image_path} due to missing CairoSVG module") + width, height = extra.get('width'), extra.get('height') + cairosvg.svg2png( + url=str(image_path), write_to=dest, + output_width=width, output_height=height + ) + else: + suffix = filepath.suffix + if not suffix: + raise ValueError( + "A suffix is missing for destination, it is needed to determine file " + "format") + if not suffix in Image.EXTENSION: + Image.init() + try: + im_format = Image.EXTENSION[suffix] + except KeyError: + raise ValueError( + "Dest image format can't be determined, {suffix!r} suffix is unknown" + ) + im = Image.open(image_path) + im.save(dest, format=im_format) + + log.debug(f"image {image_path} has been converted to {filepath}") + return filepath + + +def convert(image_path, dest=None, extra=None): + """Convert an image to a new file, and return its path + + @param image_path(str, Path): path of the image to convert + @param dest(None, Path, file): where the converted image must be stored, can be: + - None: use a temporary file + - Path: path to the file to create/overwrite + - file: a file object which must be opened for writing in binary mode + @param extra(None, dict): conversion options + if image_path link to a SVG file, following options can be used: + - width: destination width + - height: destination height + @return (Path): path of the converted file. + a generic name is used if dest is an unnamed file like object + """ + image_path = Path(image_path) + if not image_path.is_file(): + raise ValueError(f"There is no file at {image_path}!") + if extra is None: + extra = {} + return threads.deferToThread(_convertBlocking, image_path, dest, extra) + + def guess_type(source): """Guess image media type @@ -109,5 +197,3 @@ return Image.MIME[img.format] except KeyError: return None - -