Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions appstore/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ def app_image_by_id(key):
if hw in app.asset_collections:
screenshot = app.asset_collections[hw].screenshots[0]
if screenshot:
screenshots[hw] = generate_image_url(screenshot, *plat_dimensions[hw], True)
screenshots[hw] = screenshot

icon = None
if app.type == 'watchapp':
icon = generate_image_url(app.icon_large, 80, 80, True)
icon = app.icon_large
png = generate_preview_image(title=app.title, developer=app.developer.name, icon=icon, screenshots=screenshots)
response = make_response(png)
response.headers.set('Content-Type', 'image/png')
Expand Down
57 changes: 41 additions & 16 deletions appstore/image.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import os
import io
import requests

from flask import Flask
from PIL import Image, ImageDraw, ImageFont, ImageChops
from concurrent.futures import ThreadPoolExecutor, as_completed
from math import ceil

from .utils import valid_platforms
from .s3 import download_asset
from .utils import valid_platforms, plat_dimensions

parent_app = None

Expand Down Expand Up @@ -69,16 +70,33 @@ def preferred_grouping(platforms):
if len(selection) == len(selection & platforms):
return selection

def load_image_from_url(url, fallback):
def load_image_from_id(id, fallback):
try:
resp = requests.get(url, timeout=5)
resp.raise_for_status()
return Image.open(io.BytesIO(resp.content)).convert("RGBA")
file = io.BytesIO()
download_asset(id, file)
return Image.open(file).convert("RGBA")
except Exception as e:
if fallback:
return fallback
raise e

def load_images_parallel(ids_with_fallbacks):
output = {}
with ThreadPoolExecutor(max_workers=5) as executor:
future_to_key = {
executor.submit(load_image_from_id, id, fallback): key
for key, (id, fallback) in ids_with_fallbacks.items()
}

for future in as_completed(future_to_key):
key = future_to_key[future]
try:
output[key] = future.result()
except Exception as e:
output[key] = None

return output

def draw_text_ellipsized(draw, text, font, xy, max_width):
if draw.textlength(text, font=font) <= max_width:
draw.text(xy, text, font=font, fill=text_color)
Expand All @@ -96,17 +114,17 @@ def draw_text_ellipsized(draw, text, font, xy, max_width):

draw.text(xy, trimmed + ellipsis, font=font, fill=text_color)

def platform_image_in_border(canvas, image_url, top_left, platform):
def platform_image_in_border(canvas, image, top_left, platform):
border = platform_borders[platform]
img = load_image_from_url(image_url, border['fallback'])
image = image.resize(plat_dimensions[platform], resample=Image.NEAREST)

if platform == 'chalk':
img.putalpha(chalk_mask)
image.putalpha(chalk_mask)

ix = top_left[0] + border['offset'][0]
iy = top_left[1] + border['offset'][1]

canvas.alpha_composite(img, (ix, iy))
canvas.alpha_composite(image, (ix, iy))

canvas.alpha_composite(border['image'], top_left)

Expand All @@ -117,10 +135,20 @@ def generate_preview_image(title, developer, icon, screenshots):
platforms = preferred_grouping(screenshots.keys())
start_x = ceil((canvas.width - sum(platform_borders[platform]['image'].width for platform in platforms)) / 2)

image_ids = {}

if icon:
image_ids['icon'] = (icon, None)

for platform in platforms:
image_ids[platform] = (screenshots[platform], platform_borders[platform]['fallback'])

loaded_images = load_images_parallel(image_ids)

for platform in platforms:
platform_image_in_border(
canvas=canvas,
image_url=screenshots[platform],
image=loaded_images[platform],
top_left=(start_x, 0),
platform=platform
)
Expand All @@ -130,13 +158,10 @@ def generate_preview_image(title, developer, icon, screenshots):
author_position = base_author_position
text_space = base_text_space

icon_image = None
try:
icon_image = load_image_from_url(icon, None)
except Exception as e:
icon_image = None
icon_image = loaded_images['icon'] if 'icon' in loaded_images else None

if icon_image:
icon_image = icon_image.resize((80,80))
icon_image.putalpha(ImageChops.multiply(icon_mask, icon_image.split()[3]))
canvas.alpha_composite(icon_image, icon_position)
title_position = (title_position[0] + 88, title_position[1])
Expand Down