Tightened up how we serve images.

master
Pacman Ghost 5 years ago
parent b10adaac99
commit 15f693fc05
  1. 2
      vasl_templates/webapp/data/default-template-pack/atmm.j2
  2. 2
      vasl_templates/webapp/data/default-template-pack/baz.j2
  3. 2
      vasl_templates/webapp/data/default-template-pack/extras/kgs/grenade-bundles.j2
  4. 2
      vasl_templates/webapp/data/default-template-pack/extras/kgs/molotov-cocktails.j2
  5. 2
      vasl_templates/webapp/data/default-template-pack/mol-p.j2
  6. 2
      vasl_templates/webapp/data/default-template-pack/mol.j2
  7. 2
      vasl_templates/webapp/data/default-template-pack/ob_ordnance.j2
  8. 2
      vasl_templates/webapp/data/default-template-pack/ob_ordnance_ma_notes.j2
  9. 2
      vasl_templates/webapp/data/default-template-pack/ob_ordnance_note.j2
  10. 2
      vasl_templates/webapp/data/default-template-pack/ob_setup.j2
  11. 2
      vasl_templates/webapp/data/default-template-pack/ob_vehicle_note.j2
  12. 2
      vasl_templates/webapp/data/default-template-pack/ob_vehicles.j2
  13. 2
      vasl_templates/webapp/data/default-template-pack/ob_vehicles_ma_notes.j2
  14. 2
      vasl_templates/webapp/data/default-template-pack/pf.j2
  15. 2
      vasl_templates/webapp/data/default-template-pack/piat.j2
  16. 4
      vasl_templates/webapp/data/default-template-pack/players.j2
  17. 2
      vasl_templates/webapp/data/default-template-pack/psk.j2
  18. 7
      vasl_templates/webapp/files.py
  19. 59
      vasl_templates/webapp/snippets.py
  20. 58
      vasl_templates/webapp/utils.py
  21. 29
      vasl_templates/webapp/vo_notes.py

@ -16,7 +16,7 @@ td { margin: 0 ; padding: 0 ; }
padding: 2px 5px ; padding: 2px 5px ;
font-weight: bold ; font-weight: bold ;
"> ">
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?height=11">&nbsp;{%endif%}Anti-Tank Magnetic Mines {%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}">&nbsp;{%endif%}Anti-Tank Magnetic Mines
<tr> <tr>
<td style="padding:2px 5px;"> <td style="padding:2px 5px;">

@ -18,7 +18,7 @@ td.r { text-align: right ; }
padding: 2px 5px ; padding: 2px 5px ;
font-weight: bold ; font-weight: bold ;
"> ">
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?height=11">&nbsp;{%endif%}Bazooka {%if BAZ_TYPE%} ('{{BAZ_TYPE}}) {%endif%} {%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}">&nbsp;{%endif%}Bazooka {%if BAZ_TYPE%} ('{{BAZ_TYPE}}) {%endif%}
<tr> <tr>

@ -14,7 +14,7 @@ td { margin: 0 ; padding: 0 ; }
<tr> <tr>
<td colspan="2" style="background: {{PLAYER_COLORS["german"][0]}} ; border-bottom: 1px solid {{PLAYER_COLORS["german"][2]}} ; padding: 2px 5px ; font-weight: bold ;"> <td colspan="2" style="background: {{PLAYER_COLORS["german"][0]}} ; border-bottom: 1px solid {{PLAYER_COLORS["german"][2]}} ; padding: 2px 5px ; font-weight: bold ;">
{%if PLAYER_FLAGS["german"]%}<img src="{{PLAYER_FLAGS["german"]}}?height=11">&nbsp;{%endif%}Grenade Bundles {%if PLAYER_FLAGS["german"]%}<img src="{{PLAYER_FLAGS["german"]}}">&nbsp;{%endif%}Grenade Bundles
<tr> <tr>
<td style="padding:2px 5px;"> <td style="padding:2px 5px;">

@ -15,7 +15,7 @@ ul { margin: 0 0 0 10px ; padding: 0 ; }
<tr> <tr>
<td colspan="2" style="background: {{PLAYER_COLORS["german"][0]}} ; border-bottom: 1px solid {{PLAYER_COLORS["german"][2]}} ; padding: 2px 5px ; font-weight: bold ;"> <td colspan="2" style="background: {{PLAYER_COLORS["german"][0]}} ; border-bottom: 1px solid {{PLAYER_COLORS["german"][2]}} ; padding: 2px 5px ; font-weight: bold ;">
{%if PLAYER_FLAGS["german"]%}<img src="{{PLAYER_FLAGS["german"]}}?height=11">&nbsp;{%endif%}Molotov Cocktails {%if PLAYER_FLAGS["german"]%}<img src="{{PLAYER_FLAGS["german"]}}">&nbsp;{%endif%}Molotov Cocktails
<tr> <tr>
<td style="padding:0 5px;"> <td style="padding:0 5px;">

@ -19,7 +19,7 @@ ul { margin: 0 0 0 10px ; padding: 0 ; }
padding: 2px 5px ; padding: 2px 5px ;
font-weight: bold ; font-weight: bold ;
"> ">
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?height=11">&nbsp;{%endif%}MOL Projector {%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}">&nbsp;{%endif%}MOL Projector
<tr> <tr>

@ -17,7 +17,7 @@ ul { margin: 0 0 0 10px ; padding: 0 ; }
padding: 2px 5px ; padding: 2px 5px ;
font-weight: bold ; font-weight: bold ;
"> ">
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?height=11">&nbsp;{%endif%}Molotov Cocktail {%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}">&nbsp;{%endif%}Molotov Cocktail
<tr> <tr>
<td style="padding:0 5px;"> <td style="padding:0 5px;">

@ -20,7 +20,7 @@ sup { font-size: 75% ; }
padding: 2px 5px ; padding: 2px 5px ;
font-weight: bold ; font-weight: bold ;
"> ">
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?height=11">&nbsp;{%endif%}{{PLAYER_NAME}} Ordnance {%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}">&nbsp;{%endif%}{{PLAYER_NAME}} Ordnance
{%for ord in OB_ORDNANCE%} {%for ord in OB_ORDNANCE%}
<tr style="border-bottom:1px dotted #e0e0e0;"> <tr style="border-bottom:1px dotted #e0e0e0;">

@ -21,7 +21,7 @@ sup { font-size: 75% ; }
padding: 2px 5px ; padding: 2px 5px ;
font-weight: bold ; font-weight: bold ;
"> ">
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?height=11">&nbsp;{%endif%}{{PLAYER_NAME}} Ordnance Notes {%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}">&nbsp;{%endif%}{{PLAYER_NAME}} Ordnance Notes
{%if OB_ORDNANCE_MA_NOTES%} {%if OB_ORDNANCE_MA_NOTES%}
<tr> <td style="padding: 0 5px;"> <tr> <td style="padding: 0 5px;">

@ -13,7 +13,7 @@
padding: 2px 5px ; padding: 2px 5px ;
font-weight: bold ; font-weight: bold ;
"> ">
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?height=11">&nbsp;{%endif%}{{ORDNANCE_NAME}} {%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}">&nbsp;{%endif%}{{ORDNANCE_NAME}}
<tr> <tr>
<td> <img src="{{ORDNANCE_NOTE_URL}}"> <td> <img src="{{ORDNANCE_NOTE_URL}}">

@ -14,7 +14,7 @@
font-weight: bold ; font-weight: bold ;
{%if OB_SETUP_WIDTH%} width: {{OB_SETUP_WIDTH}} ; {%endif%} {%if OB_SETUP_WIDTH%} width: {{OB_SETUP_WIDTH}} ; {%endif%}
"> ">
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?height=11">&nbsp;{%endif%}{{OB_SETUP}} {%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}">&nbsp;{%endif%}{{OB_SETUP}}
</table> </table>

@ -13,7 +13,7 @@
padding: 2px 5px ; padding: 2px 5px ;
font-weight: bold ; font-weight: bold ;
"> ">
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?height=11">&nbsp;{%endif%}{{VEHICLE_NAME}} {%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}">&nbsp;{%endif%}{{VEHICLE_NAME}}
<tr> <tr>
<td> <img src="{{VEHICLE_NOTE_URL}}"> <td> <img src="{{VEHICLE_NOTE_URL}}">

@ -20,7 +20,7 @@ sup { font-size: 75% ; }
padding: 2px 5px ; padding: 2px 5px ;
font-weight: bold ; font-weight: bold ;
"> ">
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?height=11">&nbsp;{%endif%}{{PLAYER_NAME}} Vehicles {%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}">&nbsp;{%endif%}{{PLAYER_NAME}} Vehicles
{%for veh in OB_VEHICLES%} {%for veh in OB_VEHICLES%}
<tr style="border-bottom:1px dotted #e0e0e0;"> <tr style="border-bottom:1px dotted #e0e0e0;">

@ -21,7 +21,7 @@ sup { font-size: 75% ; }
padding: 2px 5px ; padding: 2px 5px ;
font-weight: bold ; font-weight: bold ;
"> ">
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?height=11">&nbsp;{%endif%}{{PLAYER_NAME}} Vehicle Notes {%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}">&nbsp;{%endif%}{{PLAYER_NAME}} Vehicle Notes
{%if OB_VEHICLES_MA_NOTES%} {%if OB_VEHICLES_MA_NOTES%}
<tr> <td style="padding: 0 5px;"> <tr> <td style="padding: 0 5px;">

@ -18,7 +18,7 @@ td.r { text-align: right ; }
padding: 2px 5px ; padding: 2px 5px ;
font-weight: bold ; font-weight: bold ;
"> ">
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?height=11">&nbsp;{%endif%}Panzerfaust {%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}">&nbsp;{%endif%}Panzerfaust
<tr> <tr>

@ -18,7 +18,7 @@ td.r { text-align: right ; }
padding: 2px 5px ; padding: 2px 5px ;
font-weight: bold ; font-weight: bold ;
"> ">
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?height=11">&nbsp;{%endif%}PIAT {%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}">&nbsp;{%endif%}PIAT
<tr> <tr>

@ -10,8 +10,8 @@
"> ">
<td style="font-weight:bold;"> <td style="font-weight:bold;">
{%if PLAYER_FLAG_1%}<img src="{{PLAYER_FLAG_1}}?height=11">&nbsp;{%endif%}{{PLAYER_1_NAME}}: <br> {%if PLAYER_FLAG_1%}<img src="{{PLAYER_FLAG_1}}">&nbsp;{%endif%}{{PLAYER_1_NAME}}: <br>
{%if PLAYER_FLAG_2%}<img src="{{PLAYER_FLAG_2}}?height=11">&nbsp;{%endif%}{{PLAYER_2_NAME}}: {%if PLAYER_FLAG_2%}<img src="{{PLAYER_FLAG_2}}">&nbsp;{%endif%}{{PLAYER_2_NAME}}:
<td> <td>
ELR: {{PLAYER_1_ELR}} <br> ELR: {{PLAYER_1_ELR}} <br>
ELR: {{PLAYER_2_ELR}} ELR: {{PLAYER_2_ELR}}

@ -18,7 +18,7 @@ td.r { text-align: right ; }
padding: 2px 5px ; padding: 2px 5px ;
font-weight: bold ; font-weight: bold ;
"> ">
{%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}?height=11">&nbsp;{%endif%}Panzerschrek {%if PLAYER_FLAG%}<img src="{{PLAYER_FLAG}}">&nbsp;{%endif%}Panzerschrek
<tr> <tr>

@ -10,6 +10,7 @@ from flask import send_file, send_from_directory, jsonify, redirect, url_for, ab
from vasl_templates.webapp import app from vasl_templates.webapp import app
from vasl_templates.webapp.file_server.vasl_mod import get_vasl_mod from vasl_templates.webapp.file_server.vasl_mod import get_vasl_mod
from vasl_templates.webapp.utils import resize_image_response
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
@ -24,6 +25,9 @@ class FileServer:
def serve_file( self, path ): def serve_file( self, path ):
"""Serve a file.""" """Serve a file."""
# NOTE: We return a Flask Response object, instead of the file data, so that (1) we can use
# send_from_directory() and (2) if we have to download a file from a URL, we can include
# the MIME type in what we return.
if FileServer.is_remote_path( self.base_dir ): if FileServer.is_remote_path( self.base_dir ):
url = "{}/{}".format( self.base_dir, path ) url = "{}/{}".format( self.base_dir, path )
# NOTE: We download the target file and serve it ourself (instead of just redirecting) # NOTE: We download the target file and serve it ourself (instead of just redirecting)
@ -55,7 +59,8 @@ def get_user_file( path ):
dname = app.config.get( "USER_FILES_DIR" ) dname = app.config.get( "USER_FILES_DIR" )
if not dname: if not dname:
abort( 404 ) abort( 404 )
return FileServer( dname ).serve_file( path ) resp = FileServer( dname ).serve_file( path )
return resize_image_response( resp )
# --------------------------------------------------------------------- # ---------------------------------------------------------------------

@ -6,6 +6,7 @@ import re
import zipfile import zipfile
import io import io
import base64 import base64
import threading
from flask import request, jsonify, send_file, abort from flask import request, jsonify, send_file, abort
from PIL import Image from PIL import Image
@ -137,6 +138,9 @@ def make_snippet_image():
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
_flag_image_cache = {}
_flag_image_cache_lock = threading.Lock()
@app.route( "/flags/<nat>" ) @app.route( "/flags/<nat>" )
def get_flag( nat ): def get_flag( nat ):
"""Get a flag image.""" """Get a flag image."""
@ -145,25 +149,36 @@ def get_flag( nat ):
if not re.search( "^[-a-z]+$", nat ): if not re.search( "^[-a-z]+$", nat ):
abort( 404 ) abort( 404 )
fname = "static/images/flags/{}.png".format( nat ) # check how we should resize the image
with app.open_resource( fname, "rb" ) as fp: # NOTE: Resizing images in the HTML snippets looks dreadful (presumably
# because VASSAL's HTML engine is so ancient), so we do it ourself :-/
# load the image default_height = app.config.get( "DEFAULT_FLAG_HEIGHT", 11 )
img = Image.open( fp ) height = int( request.args.get( "height", default_height ) )
if height <= 0:
# check if we should resize the image abort( 400 )
# NOTE: Resizing images in the HTML snippets looks dreadful (presumably
# because VASSAL's HTML engine is so ancient), so we do it ourself :-/ with _flag_image_cache_lock:
height = request.args.get( "height" )
if height: # check if we have the image in the cache
height = int( height ) cache_key = ( nat, height )
if height > 0: if cache_key not in _flag_image_cache:
width = img.size[0] / ( float(img.size[1]) / height )
width = int( width + 0.5 ) # nope - load it
img = img.resize( (width,height), Image.ANTIALIAS ) fname = "static/images/flags/{}.png".format( nat )
with app.open_resource( fname, "rb" ) as fp:
# return the image img = Image.open( fp )
buf = io.BytesIO() # resize the image
img.save( buf, format="PNG" ) height = int( height )
buf.seek( 0 ) if height > 0:
return send_file( buf, mimetype="image/png" ) width = img.size[0] / ( float(img.size[1]) / height )
width = int( width + 0.5 )
img = img.resize( (width,height), Image.ANTIALIAS )
# add the image to the cache
buf = io.BytesIO()
img.save( buf, format="PNG" )
buf.seek( 0 )
_flag_image_cache[ cache_key ] = buf.read()
# return the flag image
img_data =_flag_image_cache[ cache_key ]
return send_file( io.BytesIO(img_data), mimetype="image/png" )

@ -1,10 +1,14 @@
""" Miscellaneous utilities. """ """ Miscellaneous utilities. """
import os import os
import io
import tempfile import tempfile
import pathlib import pathlib
from collections import defaultdict from collections import defaultdict
from flask import request, Response, send_file
from PIL import Image
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
class MsgStore: class MsgStore:
@ -75,6 +79,60 @@ class TempFile:
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def resize_image_response( resp, default_width=None, default_height=None, default_scaling=None ):
"""Resize an image that will be returned as a Flask response."""
assert isinstance( resp, Response )
def get_image():
"""Get the the image from the Flask response that was passed in."""
resp.direct_passthrough = False
buf = io.BytesIO()
buf.write( resp.get_data() )
buf.seek( 0 )
return Image.open( buf )
# check if the caller specified a width and/or height
width = request.args.get( "width", default_width )
height = request.args.get( "height", default_height )
if width and height:
# width and height were specified, just use them as-is
img = get_image()
width = int( width )
height = int( height )
elif width and not height:
# width only, calculate the height
img = get_image()
aspect_ratio = float(img.size[0]) / float(img.size[1])
height = int(width) / aspect_ratio
elif not width and height:
# height only, calculate the width
img = get_image()
aspect_ratio = float(img.size[0]) / float(img.size[1])
width = int(height) * aspect_ratio
elif not width and not height:
# check if the caller specified a scaling factor
scaling = request.args.get( "scaling", default_scaling )
if scaling and scaling != 100:
img = get_image()
width = img.size[0] * float(scaling)/100
height = img.size[1] * float(scaling)/100
# check if we need to resize the image
if width or height:
assert width and height
# yup - make it so
img = img.resize( (int(width),int(height)), Image.ANTIALIAS )
buf = io.BytesIO()
img.save( buf, format="PNG" )
buf.seek( 0 )
return send_file( buf, mimetype="image/png" )
else:
# nope - return the image as-is
return resp
# ---------------------------------------------------------------------
def change_extn( fname, extn ): def change_extn( fname, extn ):
"""Change a filename's extension.""" """Change a filename's extension."""
return pathlib.Path( fname ).with_suffix( extn ) return pathlib.Path( fname ).with_suffix( extn )

@ -3,16 +3,14 @@
import os import os
import threading import threading
import io
import logging import logging
from collections import defaultdict from collections import defaultdict
from flask import request, render_template, send_file, jsonify, abort from flask import render_template, jsonify, abort
from PIL import Image
from vasl_templates.webapp import app from vasl_templates.webapp import app
from vasl_templates.webapp.files import FileServer from vasl_templates.webapp.files import FileServer
from vasl_templates.webapp.utils import is_image_file from vasl_templates.webapp.utils import is_image_file, resize_image_response
_vo_notes_lock = threading.RLock() # nb: this controls the cached V/O notes and the FileServer _vo_notes_lock = threading.RLock() # nb: this controls the cached V/O notes and the FileServer
_cached_vo_notes = None _cached_vo_notes = None
@ -127,27 +125,8 @@ def get_vo_note( vo_type, nat, key ):
fname = vo_notes.get( nat, {} ).get( key ) fname = vo_notes.get( nat, {} ).get( key )
resp = _vo_notes_file_server.serve_file( fname ) resp = _vo_notes_file_server.serve_file( fname )
# check if we should resize the file default_scaling = app.config.get( "CHAPTER_H_NOTE_SCALING", 100 )
scaling = request.args.get( "scaling" ) # nb: allow individual notes to set their scaling return resize_image_response( resp, default_scaling=default_scaling )
if not scaling:
scaling = app.config.get( "CHAPTER_H_NOTE_SCALING", 100 )
if scaling == 100:
# nope - just return the file as it is
return resp
else:
# yup - make it so
buf = io.BytesIO()
resp.direct_passthrough = False
buf.write( resp.get_data() )
buf.seek( 0 )
img = Image.open( buf )
width = int( img.size[0] * float(scaling) / 100 )
height = int( img.size[1] * float(scaling) / 100 )
img = img.resize( (width,height), Image.ANTIALIAS )
buf = io.BytesIO()
img.save( buf, format="PNG" )
buf.seek( 0 )
return send_file( buf, mimetype="image/png" )
# --------------------------------------------------------------------- # ---------------------------------------------------------------------

Loading…
Cancel
Save