forked from CGM_Public/pretix_original
Email: make responsive and show header image in MS Outlook (#2138)
This commit is contained in:
committed by
GitHub
parent
59e92245de
commit
0c6971ff5f
@@ -640,13 +640,14 @@ def convert_image_to_cid(image_src, cid_id, verify_ssl=True):
|
||||
image_src = normalize_image_url(image_src)
|
||||
|
||||
path = urlparse(image_src).path
|
||||
guess_subtype = os.path.splitext(path)[1][1:]
|
||||
image_type = os.path.splitext(path)[1][1:]
|
||||
|
||||
response = requests.get(image_src, verify=verify_ssl)
|
||||
mime_image = MIMEImage(
|
||||
response.content, _subtype=guess_subtype)
|
||||
response.content, _subtype=image_type)
|
||||
|
||||
mime_image.add_header('Content-ID', '<%s>' % cid_id)
|
||||
mime_image.add_header('Content-Disposition', 'inline;\n filename="{}.{}"'.format(cid_id, image_type))
|
||||
|
||||
return mime_image
|
||||
except:
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
{% load eventurl %}
|
||||
{% load i18n %}
|
||||
{% load thumb %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=false">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{subject}}</title>
|
||||
<!--[if gte mso 9]><xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml><![endif]-->
|
||||
<style type="text/css">
|
||||
body {
|
||||
background-color: #eee;
|
||||
@@ -208,21 +214,17 @@
|
||||
<table width="100%"><tr><td align="center">
|
||||
<table width="600"><tr><td align="center"
|
||||
<![endif]-->
|
||||
<table class="layout" width="600" border="0" cellspacing="0">
|
||||
<table class="layout" style="max-width:600px" border="0" cellspacing="0">
|
||||
{% if event.settings.logo_image %}
|
||||
<!--[if !mso]><!-- -->
|
||||
<tr>
|
||||
<td style="line-height: 0; {% if event.settings.logo_image_large %}padding: 0;{% endif %}" align="center" class="logo">
|
||||
{% if event.settings.logo_image_large %}
|
||||
<img src="{% if event.settings.logo_image|thumb:'1170x5000'|first == '/' %}{{ site_url }}{% endif %}{{ event.settings.logo_image|thumb:'1170x5000' }}" alt="{{ event.name }}"
|
||||
style="height: auto; max-width: 100%;" />
|
||||
<img src="{% if event.settings.logo_image|thumb:'600_x5000'|first == '/' %}{{ site_url }}{% endif %}{{ event.settings.logo_image|thumb:'600_x5000' }}" alt="{{ event.name }}" style="width:100%" />
|
||||
{% else %}
|
||||
<img src="{% if event.settings.logo_image|thumb:'5000x120'|first == '/' %}{{ site_url }}{% endif %}{{ event.settings.logo_image|thumb:'5000x120' }}" alt="{{ event.name }}"
|
||||
style="height: auto; max-width: 100%;" />
|
||||
<img src="{% if event.settings.logo_image|thumb:'600_x120'|first == '/' %}{{ site_url }}{% endif %}{{ event.settings.logo_image|thumb:'600_x120' }}" alt="{{ event.name }}" style="width:100%" />
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<!--<![endif]-->
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td class="header" align="center">
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import hashlib
|
||||
import math
|
||||
from io import BytesIO
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
@@ -34,18 +35,85 @@ class ThumbnailError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
"""
|
||||
|
||||
# How "size" works:
|
||||
|
||||
|
||||
## normal resize
|
||||
|
||||
image|thumb:"100x100" resizes the image proportionally to a maximum width and maximum height of 100px.
|
||||
I.e. an image of 200x100 will be resized to 100x50.
|
||||
An image of 40x80 will stay 40x80.
|
||||
|
||||
|
||||
## cropped resize with ^
|
||||
|
||||
image|thumb:"100x100^" resizes the image proportionally to a minimum width and minimum height of 100px and then will be cropped to 100x100.
|
||||
I.e. an image of 300x200 will be resized to 150x100 and then cropped from center to 100x100.
|
||||
An image of 40x80 will stay 40x80.
|
||||
|
||||
|
||||
## min-size resize with _
|
||||
|
||||
min-size-operator "_" works for width and height independently, so the following is possible:
|
||||
|
||||
image|thumb:"100_x100" resizes the image to a maximum height of 100px (if it is lower, it does not upscale) and makes it at least 100px wide
|
||||
(if the resized image would be less than 100px wide it adds a white background to both sides to make it at least 100px wide).
|
||||
I.e. an image of 300x200 will be resized to 150x100.
|
||||
An image of 40x80 will stay 40x80 but padded with a white background to be 100x80.
|
||||
|
||||
image|thumb:"100x100_" resizes the image to a maximum width of 100px (if it is lower, it does not upscale) and makes it at least 100px high
|
||||
(if the resized image would be less than 100px high it adds a white background to top and bottom to make it at least 100px high).
|
||||
I.e. an image of 400x200 will be resized to 100x50 and then padded from cener to be 100x100.
|
||||
An image of 40x80 will stay 40x80 but padded with a white background to be 40x100.
|
||||
|
||||
image|thumb:"100_x100_" resizes the image proportionally to either a width or height of 100px – it takes the smaller side and resizes that to 100px,
|
||||
so the longer side will at least be 100px. So the resulting image will at least be 100px wide and at least 100px high. If the original image is bigger
|
||||
than 100x100 then no padding will occur. If the original image is smaller than 100x100, no resize will happen but padding to 100x100 will occur.
|
||||
I.e. an image of 400x200 will be resized to 200x100.
|
||||
An image of 40x80 will stay 40x80 but padded with a white background to be 100x100.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def get_minsize(size):
|
||||
if "_" not in size:
|
||||
return (0, 0)
|
||||
min_width = 0
|
||||
min_height = 0
|
||||
if "x" in size:
|
||||
sizes = size.split('x')
|
||||
if sizes[0].endswith("_"):
|
||||
min_width = int(sizes[0][:-1])
|
||||
if sizes[1].endswith("_"):
|
||||
min_height = int(sizes[1][:-1])
|
||||
elif size.endswith("_"):
|
||||
min_width = int(size[:-1])
|
||||
min_height = min_width
|
||||
return (min_width, min_height)
|
||||
|
||||
|
||||
def get_sizes(size, imgsize):
|
||||
crop = False
|
||||
if size.endswith('^'):
|
||||
crop = True
|
||||
size = size[:-1]
|
||||
|
||||
if crop and "_" in size:
|
||||
raise ThumbnailError('Size %s has errors: crop and minsize cannot be combined.' % size)
|
||||
|
||||
min_width, min_height = get_minsize(size)
|
||||
if min_width or min_height:
|
||||
size = size.replace("_", "")
|
||||
|
||||
if 'x' in size:
|
||||
size = [int(p) for p in size.split('x')]
|
||||
else:
|
||||
size = [int(size), int(size)]
|
||||
|
||||
if crop:
|
||||
# currently crop and min-size cannot be combined
|
||||
wfactor = min(1, size[0] / imgsize[0])
|
||||
hfactor = min(1, size[1] / imgsize[1])
|
||||
if wfactor == hfactor:
|
||||
@@ -61,6 +129,14 @@ def get_sizes(size, imgsize):
|
||||
else:
|
||||
wfactor = min(1, size[0] / imgsize[0])
|
||||
hfactor = min(1, size[1] / imgsize[1])
|
||||
if min_width and min_height:
|
||||
wfactor = max(wfactor, hfactor)
|
||||
hfactor = wfactor
|
||||
elif min_width:
|
||||
wfactor = hfactor
|
||||
elif min_height:
|
||||
hfactor = wfactor
|
||||
|
||||
if wfactor == hfactor:
|
||||
return (int(imgsize[0] * hfactor), int(imgsize[1] * wfactor)), None
|
||||
elif wfactor < hfactor:
|
||||
@@ -69,6 +145,32 @@ def get_sizes(size, imgsize):
|
||||
return (int(imgsize[0] * hfactor), size[1]), None
|
||||
|
||||
|
||||
def resize_image(image, size):
|
||||
# before we calc thumbnail, we need to check and apply EXIF-orientation
|
||||
image = ImageOps.exif_transpose(image)
|
||||
|
||||
new_size, crop = get_sizes(size, image.size)
|
||||
image = image.resize(new_size, resample=LANCZOS)
|
||||
if crop:
|
||||
image = image.crop(crop)
|
||||
|
||||
min_width, min_height = get_minsize(size)
|
||||
|
||||
if min_width > new_size[0] or min_height > new_size[1]:
|
||||
padding = math.ceil(max(min_width - new_size[0], min_height - new_size[1]) / 2)
|
||||
image = image.convert('RGB')
|
||||
image = ImageOps.expand(image, border=padding, fill="white")
|
||||
|
||||
new_width = max(min_width, new_size[0])
|
||||
new_height = max(min_height, new_size[1])
|
||||
new_x = (image.width - new_width) // 2
|
||||
new_y = (image.height - new_height) // 2
|
||||
|
||||
image = image.crop((new_x, new_y, new_x + new_width, new_y + new_height))
|
||||
|
||||
return image
|
||||
|
||||
|
||||
def create_thumbnail(sourcename, size):
|
||||
source = default_storage.open(sourcename)
|
||||
image = Image.open(BytesIO(source.read()))
|
||||
@@ -77,13 +179,7 @@ def create_thumbnail(sourcename, size):
|
||||
except:
|
||||
raise ThumbnailError('Could not load image')
|
||||
|
||||
# before we calc thumbnail, we need to check and apply EXIF-orientation
|
||||
image = ImageOps.exif_transpose(image)
|
||||
|
||||
scale, crop = get_sizes(size, image.size)
|
||||
image = image.resize(scale, resample=LANCZOS)
|
||||
if crop:
|
||||
image = image.crop(crop)
|
||||
image = resize_image(image, size)
|
||||
|
||||
if source.name.endswith('.jpg') or source.name.endswith('.jpeg'):
|
||||
# Yields better file sizes for photos
|
||||
|
||||
116
src/tests/helpers/test_thumb.py
Normal file
116
src/tests/helpers/test_thumb.py
Normal file
@@ -0,0 +1,116 @@
|
||||
#
|
||||
# This file is part of pretix (Community Edition).
|
||||
#
|
||||
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||
#
|
||||
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||
# this file, see <https://pretix.eu/about/en/license>.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
from PIL import Image
|
||||
|
||||
from pretix.helpers.thumb import resize_image
|
||||
|
||||
|
||||
def test_no_resize():
|
||||
img = Image.new('RGB', (40, 20))
|
||||
img = resize_image(img, "100x100")
|
||||
width, height = img.size
|
||||
assert width == 40
|
||||
assert height == 20
|
||||
|
||||
img = Image.new('RGB', (40, 20))
|
||||
img = resize_image(img, "100x100^")
|
||||
width, height = img.size
|
||||
assert width == 40
|
||||
assert height == 20
|
||||
|
||||
img = Image.new('RGB', (40, 20))
|
||||
img = resize_image(img, "10_x20")
|
||||
width, height = img.size
|
||||
assert width == 40
|
||||
assert height == 20
|
||||
|
||||
img = Image.new('RGB', (40, 20))
|
||||
img = resize_image(img, "40x10_")
|
||||
width, height = img.size
|
||||
assert width == 40
|
||||
assert height == 20
|
||||
|
||||
|
||||
def test_resize():
|
||||
img = Image.new('RGB', (40, 20))
|
||||
img = resize_image(img, "10x10")
|
||||
width, height = img.size
|
||||
assert width == 10
|
||||
assert height == 5
|
||||
|
||||
img = Image.new('RGB', (40, 20))
|
||||
img = resize_image(img, "100x10")
|
||||
width, height = img.size
|
||||
assert width == 20
|
||||
assert height == 10
|
||||
|
||||
img = Image.new('RGB', (40, 20))
|
||||
img = resize_image(img, "10x100")
|
||||
width, height = img.size
|
||||
assert width == 10
|
||||
assert height == 5
|
||||
|
||||
|
||||
def test_crop():
|
||||
img = Image.new('RGB', (40, 20))
|
||||
img = resize_image(img, "10x10^")
|
||||
width, height = img.size
|
||||
assert width == 10
|
||||
assert height == 10
|
||||
|
||||
|
||||
def test_minsize():
|
||||
img = Image.new('RGB', (60, 20))
|
||||
img = resize_image(img, "10_x10")
|
||||
width, height = img.size
|
||||
assert width == 30
|
||||
assert height == 10
|
||||
|
||||
img = Image.new('RGB', (10, 20))
|
||||
img = resize_image(img, "10_x10")
|
||||
width, height = img.size
|
||||
assert width == 10
|
||||
assert height == 10
|
||||
|
||||
img = Image.new('RGB', (60, 20))
|
||||
img = resize_image(img, "10x10_")
|
||||
width, height = img.size
|
||||
assert width == 10
|
||||
assert height == 10
|
||||
|
||||
img = Image.new('RGB', (20, 60))
|
||||
img = resize_image(img, "10x10_")
|
||||
width, height = img.size
|
||||
assert width == 10
|
||||
assert height == 30
|
||||
|
||||
img = Image.new('RGB', (20, 60))
|
||||
img = resize_image(img, "10_x10_")
|
||||
width, height = img.size
|
||||
assert width == 10
|
||||
assert height == 30
|
||||
|
||||
img = Image.new('RGB', (20, 60))
|
||||
img = resize_image(img, "100_x100_")
|
||||
width, height = img.size
|
||||
assert width == 100
|
||||
assert height == 100
|
||||
Reference in New Issue
Block a user