Skip to content

Instantly share code, notes, and snippets.

@feluxe
Last active May 19, 2017 17:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save feluxe/e4cb86e2ea3ca72a0e821b3dfa07e175 to your computer and use it in GitHub Desktop.
Save feluxe/e4cb86e2ea3ca72a0e821b3dfa07e175 to your computer and use it in GitHub Desktop.
Example for functions with multi-line parameters in Python.
import hug
import os
from typing import Optional, Tuple, Union
from image_server.constants import DATA_DIR, MAXIMUM_WIDTH_LIMIT
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
def _make_str_from_render_query(render_query: dict):
"""
This function takes the data from the render query and produces a string that is used in the
file name of the requested image. An example of such string: 'w=1100&q=18'
"""
render_query_str: str = ''.join(
['&' + str(key) + '=' + str(val) for key, val in render_query.items() if val])
if render_query_str != '':
render_query_str: str = '?' + render_query_str[1:]
return render_query_str
def _get_path_to_requested_image(
item_info: dict,
render_query: dict
) -> str:
""""""
render_query_str: str = _make_str_from_render_query(render_query)
image_name: str = item_info['main_dir'] + '/' + item_info['sub_dir'] + '/' + item_info[
'basename'] + render_query_str + item_info['ext']
img_file: str = os.path.join(DATA_DIR, image_name)
return img_file
def _get_path_to_source_image(item_info: dict) -> str:
return DATA_DIR + '/' + item_info['main_dir'] + '/' + item_info['sub_dir'] + '/' + \
item_info['basename'] + item_info['ext']
def _draw_width_as_text_on_image(
loaded_image: Image,
new_w: int,
new_h: int
) -> None:
"""This function draws @new_w onto the image. (This is used for debugging.)"""
draw = ImageDraw.Draw(loaded_image)
font = ImageFont.truetype("Como-Regular.otf", 45)
text_w, text_h = draw.textsize(str(new_w), font=font)
draw.text(((new_w - text_w) / 2, (new_h - text_h) / 2), str(new_w), (255, 0, 0), font=font)
def _get_scaled_dims(
native_w: int,
native_h: int,
target_w: Optional[int] = None,
target_h: Optional[int] = None
) -> Tuple[int, int]:
"""Calculate scaled image dimensions."""
invalid_args_msg = 'Please provide a value for "target_width" OR "target_height".'
if target_w and target_h:
raise Exception(invalid_args_msg)
elif not target_w and not target_h:
raise Exception(invalid_args_msg)
elif target_w:
return target_w, int((target_w / native_w) * native_h)
elif target_h:
return int((target_h / native_h) * native_w), target_h
def _prepare_target_width(
target_width: Optional[int],
maximum_width_limit: Optional[int],
native_width: int
) -> int:
"""
If no width was requested in the URL query use native width from image.
If the resulting width exceeds @maximum_width_limit, use @maximum_width_limit.
If the resulting width exceeds the native width of the image, use native width.
"""
target_w: int = target_width or native_width
target_w: int = maximum_width_limit if maximum_width_limit and \
target_w > maximum_width_limit else target_w
target_w: int = native_width if native_width < target_w else target_w
return target_w
def _get_new_dimensions(
loaded_image: Image,
requested_w: Optional[int],
maximum_width_limit: Optional[int]
) -> tuple:
"""
Calculate new image dimensions.
This takes @requested_w and @maximum_with_limit into account.
"""
dims: tuple = loaded_image.size
native_w: int = dims[0]
native_h: int = dims[1]
target_w: int = _prepare_target_width(requested_w, maximum_width_limit, native_w)
dims_after_scale: tuple = _get_scaled_dims(native_w, native_h, target_w)
scaled_w: int = dims_after_scale[0]
scaled_h: int = dims_after_scale[1]
return scaled_w, scaled_h
def _validate_jpg_quality(input_quality: int):
return input_quality in range(0, 100)
def _process_image(
source_image: str,
requested_image: str,
render_query: dict
):
"""
Load @source_image, then use data from @render_query to render a new image. Save the new image
at the path that is @requested_image.
"""
requested_w: Optional[int] = render_query['w']
requested_q: Optional[int] = render_query['q']
maximum_width_limit: Optional[int] = render_query['maximum_width_limit']
draw_width: bool = render_query['draw_width']
loaded_image: Image = Image.open(source_image)
img_format: str = loaded_image.format
dest: str = requested_image
if requested_w or draw_width:
new_dims: tuple = _get_new_dimensions(loaded_image, requested_w, maximum_width_limit)
new_w: int = new_dims[0]
new_h: int = new_dims[1]
if requested_w:
loaded_image.thumbnail((new_w, new_h), Image.ANTIALIAS)
if draw_width:
_draw_width_as_text_on_image(loaded_image, new_w, new_h)
if 'JPEG' in img_format:
quality = int(requested_q) if requested_q and _validate_jpg_quality(requested_q) else False
loaded_image.save(dest, optimize=True, quality=quality, progressive=True)
elif 'PNG' in img_format:
loaded_image.save(dest, optimize=True)
else:
loaded_image.save(dest)
def _create_item_info_object(
main_dir: str,
sub_dir: str,
img_name: str
) -> dict:
"""
This dict contains data with which you can create file paths for the source file and output
file.
"""
splitted_filename: tuple = os.path.splitext(img_name)
item_info = {
'main_dir': main_dir, # type: str
'sub_dir': sub_dir, # type: str
'filename': img_name, # type: str
'basename': splitted_filename[0], # type: str
'ext': splitted_filename[1], # type: str
}
return item_info
def _create_render_query_object(
w: Optional[str],
q: Optional[str],
draw_width: Optional[str]
) -> dict:
"""
This dict contains the data that defines how the image should be rendered.
"""
make_int_or_none = lambda item: int(item) if item and item.isdigit() else None
render_query = {
'w': make_int_or_none(w), # type: Optional[int]
'q': make_int_or_none(q), # type: Optional[int]
'draw_width': draw_width,
'maximum_width_limit': make_int_or_none(MAXIMUM_WIDTH_LIMIT) # type: Optional[int]
}
return render_query
@hug.get('/images/{main_dir}/{sub_dir}/{filename}', output=hug.output_format.file)
def get_image(
main_dir: str,
sub_dir: str,
filename: str,
w: str = None,
q: str = None,
draw_width: str = None
):
"""
Serve requested image.
If the image does not exist render it according to the query, then serve it.
Future requests with the same query will skip the rendering and load the image immediately.
"""
item_info: dict = _create_item_info_object(main_dir, sub_dir, filename)
render_query: dict = _create_render_query_object(w, q, draw_width)
requested_image: str = _get_path_to_requested_image(item_info, render_query)
if not os.path.isfile(requested_image):
source_image: str = _get_path_to_source_image(item_info)
_process_image(source_image, requested_image, render_query)
return requested_image
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment