diff --git a/src/pretix/helpers/__init__.py b/src/pretix/helpers/__init__.py
index e69de29bb2..2ead6c5a02 100644
--- a/src/pretix/helpers/__init__.py
+++ b/src/pretix/helpers/__init__.py
@@ -0,0 +1,9 @@
+from django.apps import AppConfig
+
+
+class PretixHelpersConfig(AppConfig):
+ name = 'pretix.helpers'
+ label = 'pretixhelpers'
+
+
+default_app_config = 'pretix.helpers.PretixHelpersConfig'
diff --git a/src/pretix/helpers/migrations/0001_initial.py b/src/pretix/helpers/migrations/0001_initial.py
new file mode 100644
index 0000000000..0915e0ae0a
--- /dev/null
+++ b/src/pretix/helpers/migrations/0001_initial.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.11 on 2018-03-20 07:36
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Thumbnail',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('source', models.CharField(max_length=255)),
+ ('size', models.CharField(max_length=255)),
+ ('thumb', models.FileField(upload_to='')),
+ ],
+ ),
+ migrations.AlterUniqueTogether(
+ name='thumbnail',
+ unique_together=set([('source', 'size')]),
+ ),
+ ]
diff --git a/src/pretix/helpers/migrations/__init__.py b/src/pretix/helpers/migrations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/pretix/helpers/models.py b/src/pretix/helpers/models.py
new file mode 100644
index 0000000000..4e34b3179f
--- /dev/null
+++ b/src/pretix/helpers/models.py
@@ -0,0 +1,10 @@
+from django.db import models
+
+
+class Thumbnail(models.Model):
+ source = models.CharField(max_length=255)
+ size = models.CharField(max_length=255)
+ thumb = models.FileField(upload_to='pub/thumbs/')
+
+ class Meta:
+ unique_together = (('source', 'size'),)
diff --git a/src/pretix/helpers/templatetags/__init__.py b/src/pretix/helpers/templatetags/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/pretix/helpers/templatetags/thumb.py b/src/pretix/helpers/templatetags/thumb.py
new file mode 100644
index 0000000000..ddc00d35df
--- /dev/null
+++ b/src/pretix/helpers/templatetags/thumb.py
@@ -0,0 +1,18 @@
+import logging
+
+from django import template
+from django.core.files.storage import default_storage
+
+from pretix.helpers.thumb import get_thumbnail
+
+register = template.Library()
+logger = logging.getLogger(__name__)
+
+
+@register.filter
+def thumb(source, arg):
+ try:
+ return get_thumbnail(source, arg).thumb.url
+ except:
+ logger.exception('Failed to create thumbnail')
+ return default_storage.url(source)
diff --git a/src/pretix/helpers/thumb.py b/src/pretix/helpers/thumb.py
new file mode 100644
index 0000000000..48a85cacb0
--- /dev/null
+++ b/src/pretix/helpers/thumb.py
@@ -0,0 +1,75 @@
+import hashlib
+from io import BytesIO
+
+from django.core.files.base import ContentFile
+from django.core.files.storage import default_storage
+from PIL import Image
+
+from pretix.helpers.models import Thumbnail
+
+
+class ThumbnailError(Exception):
+ pass
+
+
+def get_sizes(size, imgsize):
+ crop = False
+ if size.endswith('^'):
+ crop = True
+ size = size[:-1]
+
+ if 'x' in size:
+ size = [int(p) for p in size.split('x')]
+ else:
+ size = [int(size), int(size)]
+
+ if crop:
+ wfactor = min(1, size[0] / imgsize[0])
+ hfactor = min(1, size[1] / imgsize[1])
+ if wfactor > hfactor:
+ return (int(size[0]), int(imgsize[1] * hfactor)), \
+ (0, int((imgsize[1] * wfactor - size[1]) / 2), size[0], int((imgsize[1] * wfactor + size[1]) / 2))
+ else:
+ return (int(imgsize[0] * hfactor), int(size[1])), \
+ (int((imgsize[0] * hfactor - size[0]) / 2), 0, int((imgsize[0] * hfactor + size[0]) / 2), size[1])
+ else:
+ wfactor = min(1, size[0] / imgsize[0])
+ hfactor = min(1, size[1] / imgsize[1])
+ if wfactor < hfactor:
+ return (size[0], int(imgsize[1] * wfactor)), None
+ else:
+ return (int(imgsize[0] * hfactor), size[1]), None
+
+
+def create_thumbnail(source, size):
+ if isinstance(source, str):
+ source = default_storage.open(source)
+ image = Image.open(BytesIO(source.read()))
+ try:
+ image.load()
+ except:
+ raise ThumbnailError('Could not load image')
+
+ scale, crop = get_sizes(size, image.size)
+ image = image.resize(scale)
+ print(scale, crop)
+ if crop:
+ image = image.crop(crop)
+
+ checksum = hashlib.md5(image.tobytes()).hexdigest()
+ name = checksum + '.' + size.replace('^', 'c') + '.png'
+ buffer = BytesIO()
+ image.save(fp=buffer, format='PNG')
+ imgfile = ContentFile(buffer.getvalue())
+
+ t = Thumbnail.objects.create(source=source, size=size)
+ t.thumb.save(name, imgfile)
+ return t
+
+
+def get_thumbnail(source, size):
+ # Assumes files are immutable
+ try:
+ return Thumbnail.objects.get(source=source, size=size)
+ except Thumbnail.DoesNotExist:
+ return create_thumbnail(source, size)
diff --git a/src/pretix/presale/templates/pretixpresale/event/base.html b/src/pretix/presale/templates/pretixpresale/event/base.html
index 49e2a85b32..b1b97d0e80 100644
--- a/src/pretix/presale/templates/pretixpresale/event/base.html
+++ b/src/pretix/presale/templates/pretixpresale/event/base.html
@@ -1,7 +1,7 @@
{% extends "pretixpresale/base.html" %}
{% load i18n %}
{% load staticfiles %}
-{% load thumbnail %}
+{% load thumb %}
{% load eventurl %}
{% load safelink %}
{% block thetitle %}
@@ -27,7 +27,7 @@
{% if event_logo %}
-
+
{% else %}