Skip to content

Commit

Permalink
Add viewport support. Fixes #4
Browse files Browse the repository at this point in the history
  • Loading branch information
stephrdev committed Mar 14, 2017
1 parent 08adbec commit b06c2f9
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 8 deletions.
25 changes: 23 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ call the ``ultimatethumb`` tag with proper parameters:
.. code-block:: html
{% load ultimatethumb_tags %}
{% ultimatethumb 'mythumb' mymodel.imagefield.name sizes='400x0,200x0' %}
{% ultimatethumb 'mythumb' mymodel.imagefield.name sizes='200x0,400x0' %}
<picture>
{% for source in mythumb %}
<source
srcset="{{ source.url_2x }} 2x, {{ source.url }} 1x"
{% if not forloop.last %}media="(max-width: {{ source.size.width }}px)"{% endif %}
{% if not forloop.last %}media="(max-width: {{ source.viewport.width }}px)"{% endif %}
/>
{% if forloop.last %}<img src="{{ source.url }}" />{% endif %}
{% endfor %}
Expand Down Expand Up @@ -111,6 +111,27 @@ To resize static images, just prefix the path with ``static:``, for example:
There are many other options/parameters to pass to the templatetag. Please refer
to the codebase until the documentation is more complete.
You can also pass the viewport size in addition to the requested thumbnail size:
.. code-block:: html
{% load ultimatethumb_tags %}
{% ultimatethumb 'mythumb' 'static:img/logo.jpg' sizes='400x0:600x0' %}
<img src="{{ mythumb.0.url }}" />
This will set the `thumbnail.viewport.width` to 600.
If you want so save some characters, you might short cut the sizes by leaving out
the "x0" for the auto'ed dimesion.
.. code-block:: html
{% load ultimatethumb_tags %}
{% ultimatethumb 'mythumb' 'static:img/logo.jpg' sizes='400:600' %}
<img src="{{ mythumb.0.url }}" />
The sizes are now the same as if you would use sizes='400x0,600x0'.
Options
-------
Expand Down
16 changes: 16 additions & 0 deletions ultimatethumb/tests/test_templatetags.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,22 @@ def test_pngquant(self):
assert len(context['img']) == 1
assert context['img'][0].options['pngquant'] == 10

def test_viewport(self):
source = ImageModelFactory.create(file__width=400, file__height=100)

template = Template((
'{%% load ultimatethumb_tags %%}'
'{%% ultimatethumb "img" "%s" sizes="200:600" %%}'
) % source.file.path)

context = Context()
assert template.render(context) == ''

assert 'img' in context
assert len(context['img']) == 1
assert context['img'][0].options['size'] == ['200', '0']
assert context['img'][0].options['viewport'] == ['600', '0']

def test_oversize(self):
source = ImageModelFactory.create(file__width=210, file__height=100)

Expand Down
30 changes: 28 additions & 2 deletions ultimatethumb/tests/test_thumbnail.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ def test_invalid_opts(self):
assert '`size` is required' in str(exc.value)

def test_get_name(self):
thumbnail = Thumbnail('test.jpg', {'size': ['100', '100']})
thumbnail = Thumbnail('test.jpg', {'size': ['100', '100'], 'viewport': 'ignored'})
assert thumbnail.get_name() == '821b6e68771352f0bc53acd8e8144972a56dd0ac/test.jpg'

def test_from_name(self):
thumbnail = Thumbnail('test.jpg', {'size': ['100', '100']})
thumbnail = Thumbnail('test.jpg', {'size': ['100', '100'], 'viewport': 'ignored'})
thumbnail2 = Thumbnail.from_name(thumbnail.get_name())

assert thumbnail.source == thumbnail2.source
thumbnail.options.pop('viewport')
assert thumbnail.options == thumbnail2.options

def test_get_estimated_size(self):
Expand Down Expand Up @@ -348,3 +349,28 @@ def test_estimated_size(self, input_size, thumb_size, upscale, crop, expected):
crop,
upscale
))

@pytest.mark.parametrize('input_size,thumb_size,viewport,expected', [
((200, 100), (100, 0), None, (100, 50)),
((200, 100), (100, 100), None, (100, 50)),
((200, 100), (100, 100), (100, 0), (100, None)),
((200, 100), (100, 100), (0, 100), (None, 100)),
])
def test_viewport(self, input_size, thumb_size, viewport, expected):
image = ImageModelFactory.create(
file__width=input_size[0], file__height=input_size[1])

instance = Thumbnail(image.file.path, {
'size': (str(thumb_size[0]), str(thumb_size[1])),
'viewport': (str(viewport[0]), str(viewport[1])) if viewport else None,
})

assert_error = '{0} -> {1} / {2}'.format(
input_size,
thumb_size,
viewport,
)

viewport_size = instance.viewport
assert viewport_size.width == expected[0], assert_error
assert viewport_size.height == expected[1], assert_error
37 changes: 37 additions & 0 deletions ultimatethumb/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ def test_valid_multiple(self):
assert parse_sizes('400x100,0x250,50%x0') == [
['400', '100'], ['0', '250'], ['50%', '0']]

def test_valid_short(self):
assert parse_sizes('400,250,50%') == [
['400', '0'], ['250', '0'], ['50%', '0']]

def test_valid_mixed(self):
assert parse_sizes('400x100,250,0x50%') == [
['400', '100'], ['250', '0'], ['0', '50%']]

def test_invalid(self):
with pytest.raises(ValueError):
parse_sizes('400x100,Ax250')
Expand All @@ -75,6 +83,35 @@ def test_invalid_percent(self):
with pytest.raises(ValueError):
parse_sizes('5%0x0')

def test_valid_single_with_viewport(self):
assert parse_sizes('400x100:600x200') == [['400', '100', '600', '200']]

def test_valid_multiple_with_viewport(self):
assert parse_sizes('400x100:300x100,0x250:0x500,50%x0:1000x0') == [
['400', '100', '300', '100'],
['0', '250', '0', '500'],
['50%', '0', '1000', '0']
]

def test_valid_short_with_viewport(self):
assert parse_sizes('400:600,250:500,50%:200') == [
['400', '0', '600', '0'],
['250', '0', '500', '0'],
['50%', '0', '200', '0']
]

def test_valid_mixed_with_viewport(self):
assert parse_sizes('400x100:600,250,0x50%:200x500') == [
['400', '100', '600', '0'], ['250', '0'], ['0', '50%', '200', '500']]

def test_invalid_viewport(self):
with pytest.raises(ValueError):
parse_sizes('400x100,Ax250')

def test_invalid_percent_viewport(self):
with pytest.raises(ValueError):
parse_sizes('400:50%x0')


class TestFactorSize:
def test_int(self):
Expand Down
23 changes: 21 additions & 2 deletions ultimatethumb/thumbnail.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ def get_thumbnails(self):
size = [str(source_size[0]), str(source_size[1])]
oversize = True

options = {'size': size}
options = {'size': size[0:2]}
if len(size) == 4:
options['viewport'] = size[2:4]

options.update(self.options)
thumbnails.append(Thumbnail(self.source, options))

Expand Down Expand Up @@ -112,12 +115,28 @@ def from_name(cls, name):
return Thumbnail(*get_thumb_data(name))

def get_name(self):
return get_thumb_name(self.source, **self.options)
return get_thumb_name(self.source, **dict(
(option, value)
for option, value in self.options.items()
if option not in ('viewport',)
))

@cached_property
def size(self):
return self.get_estimated_size()

@cached_property
def viewport(self):
viewport = self.options.get('viewport', None)

if not viewport:
return self.size

return Size(
int(viewport[0]) if viewport[0] != '0' else None,
int(viewport[1]) if viewport[1] != '0' else None
)

@property
def url(self):
return build_url(self.get_name())
Expand Down
9 changes: 7 additions & 2 deletions ultimatethumb/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from PIL import Image as PILImage


SIZE_RE = re.compile(r'(?:(\d+%?)x(\d+%?))')
SIZE_RE = re.compile(r'^(\d+%?)(?:x(\d+%?))?(?:\:(\d+)(?:x(\d+))?)?$')


def get_cache_key(key):
Expand Down Expand Up @@ -80,7 +80,12 @@ def parse_sizes(value):
if not parsed_size:
raise ValueError('{0} is not a valid size'.format(size))

parsed_sizes.append([parsed_size.groups()[0], parsed_size.groups()[1]])
parts = parsed_size.groups()
size = [parts[0], parts[1] or '0']
if parts[2]:
size += [parts[2], parts[3] or '0']

parsed_sizes.append(size)

return parsed_sizes

Expand Down

0 comments on commit b06c2f9

Please sign in to comment.