Source code for images

"""
List of Endpoints
=================

    - :code:`/upload/image`: Handled by :py:func:`image_upload`.
    - :code:`/upload/image/overlay`: Handled by :py:func:`image_upload_with_overlay`.

API Reference
=============
"""

from io import BytesIO

import flask
from PIL import Image, ExifTags
from flask import request, abort
from werkzeug.datastructures import FileStorage

from storage import upload_data, generate_image_path


[docs]def image_upload(): """ Endpoint for just image upload. Expects form data fields "post_id", "ext" and "content-type" which are used to save the output name. Expects image to be uploaded as multipart/file-upload and accessible under the name "image". :return: - 204 - No content. - 400 - Missing data field. - 5XX - Request too large. """ image, output, ext, content_type = _extract_base_image_form_data() _handle_image_uploading(image, output, ext, content_type) return '', 204
def _extract_base_image_form_data(): """ Extracts form data for :py:function:`image_upload`. :return: Necessary form elements :rtype:tuple """ form = request.form post_id = form.get('post_id') ext = form.get('ext') content_type = form.get('content-type') image = request.files.get('image') if None in (post_id, ext, image, content_type): abort(400, 'Missing required data field. See documentation for more details') return image, post_id, ext, content_type def _handle_image_uploading(image: BytesIO, post_id: str, ext: str, content_type: str) -> str: """ Uploads the given image to Cloud Storage at the location specified by environment variable "IMAGE_STORE". :param BytesIO image: :param output: Output filename, appended to base location. :param ext: Extension for the given image. :return: Full path location in Cloud Storage """ full_path = generate_image_path(post_id, ext) return upload_data(image, content_type, full_path)
[docs]def image_upload_with_overlay(): """ Endpoint for image + overlay upload. Has an additional form data field "overlay" that is required. :return: - 204 - No content. - 400 - Missing data field. - 5XX - Request too large. """ overlay, (image, post_id, ext, content_type) = _extract_overlay_form_data() combined_image = _combine_images(image, overlay, ext) _handle_image_uploading(combined_image, post_id, ext, content_type) return '', 204
def _combine_images(background: FileStorage, overlay: FileStorage, ext: str) -> BytesIO: """ Combines a background image and overlay using PIL. :param background: :param overlay: :param ext: :return: Object ready for uploading. :rtype:BytesIO """ background = _check_rotation_exif(Image.open(background)) overlay = _check_rotation_exif(Image.open(overlay)) background = background.convert("RGBA") overlay = overlay.convert("RGBA") overlay = overlay.resize(background.size) background.paste( overlay, (0, 0, background.size[0], background.size[1]), overlay ) background = background.convert("RGB") data = BytesIO() try: background.save(data, format=ext) except KeyError as e: abort(400, str(e)) data.seek(0) return data def _check_rotation_exif(image: Image) -> Image: """ Checks the rotational exif data on a given image and rotates it accordingly. :param image: :return: Rotated image. :rtype: PIL.Image """ for orientation in ExifTags.TAGS.keys(): if ExifTags.TAGS[orientation] == 'Orientation': try: exif = dict(image._getexif().items()) val = exif[orientation] except (AttributeError, KeyError): return image if val == 3: image = image.rotate(180, expand=True) elif val == 6: image = image.rotate(270, expand=True) elif val == 8: image = image.rotate(90, expand=True) return image return image def _extract_overlay_form_data(): """ Extracts all data required for :py:function:`image_upload_with_overlay`. :rtype:tuple """ overlay = request.files.get('overlay') if overlay is None: abort(400, 'Missing overlay upload.') return overlay, _extract_base_image_form_data()
[docs]def setup_routing(app: flask.Flask): """ Basic routing function for flask. :param flask.Flask app: Your flask application object. """ app.add_url_rule('/upload/image', endpoint='image', view_func=image_upload, methods=["POST"]) app.add_url_rule('/upload/image/overlay', endpoint='image.overlay', view_func=image_upload_with_overlay, methods=["POST"])