Browse Source

tests for thumbnailer plugin + larger/smaller options

remove debug statements

rename thumb_size -> thumb_scale_size
main
Brian Mattern 13 years ago
committed by Lakshmi Vyasarajan
parent
commit
174559ee9e
4 changed files with 287 additions and 46 deletions
  1. +72
    -15
      hyde/ext/plugins/images.py
  2. BIN
      hyde/tests/ext/images/landscape.jpg
  3. BIN
      hyde/tests/ext/images/portrait.jpg
  4. +215
    -31
      hyde/tests/ext/test_images.py

+ 72
- 15
hyde/ext/plugins/images.py View File

@@ -149,6 +149,44 @@ class ImageSizerPlugin(Plugin):

return text

def scale_aspect(a, b1, b2):
from math import ceil
"""
Scales a by b2/b1 rounding up to nearest integer
"""
return int(ceil(a * b2 / float(b1)))


def thumb_scale_size(orig_width, orig_height, dim1, dim2, preserve_orientation=False):
"""
Determine thumbnail size

Params:
orig_width, orig_height: original image dimensions
dim1, dim2: thumbnail dimensions
preserve_orientatin: whether to preserve original image's orientation

If `preserve_orientation` is True and the original image is portrait, then
dim1 corresponds to the height and dim2 corresponds to the width
Otherwise, dim1 is width and dim2 is height.
"""
if preserve_orientation and orig_height > orig_width:
width, height = dim2, dim1
else:
width, height = dim1, dim2

if width is None:
width = scale_aspect(orig_width, orig_height, height)
elif height is None:
height = scale_aspect(orig_height, orig_width, width)
elif orig_width*height >= orig_height*width:
width = scale_aspect(orig_width, orig_height, height)
else:
height = scale_aspect(orig_height, orig_width, width)

return width, height


class ImageThumbnailsPlugin(Plugin):
"""
Provide a function to get thumbnail for any image resource.
@@ -172,20 +210,26 @@ class ImageThumbnailsPlugin(Plugin):
include:
- '*.png'
- '*.jpg'
which means - make from every picture two thumbnails with different prefixes
- lfrom every picture arger: 100
prefix: thumbs3_
include:
- '*.jpg'
which means - make three thumbnails from every picture with different prefixes
and sizes

If both width and height defined, image would be cropped, you can define
crop_type as one of these values: "topleft", "center" and "bottomright".
"topleft" is default.

XXX fix docs for larger/smaller

Currently, only supports PNG and JPG.
"""

def __init__(self, site):
super(ImageThumbnailsPlugin, self).__init__(site)

def thumb(self, resource, width, height, prefix, crop_type):
def thumb(self, resource, width, height, prefix, crop_type, preserve_orientation=False):
"""
Generate a thumbnail for the given image
"""
@@ -211,17 +255,13 @@ class ImageThumbnailsPlugin(Plugin):
im = Image.open(resource.path)
if im.mode != 'RGBA':
im = im.convert('RGBA')
resize_width = width
resize_height = height
if resize_width is None:
resize_width = im.size[0]*height/im.size[1] + 1
elif resize_height is None:
resize_height = im.size[1]*width/im.size[0] + 1
elif im.size[0]*height >= im.size[1]*width:
resize_width = im.size[0]*height/im.size[1]
else:
resize_height = im.size[1]*width/im.size[0]

resize_width, resize_height = thumb_scale_size(im.size[0], im.size[1], width, height, preserve_orientation)

if preserve_orientation and im.size[1] > im.size[0]:
width, height = height, width

self.logger.debug("Resize to: %d,%d" % (resize_width, resize_height))
im = im.resize((resize_width, resize_height), Image.ANTIALIAS)
if width is not None and height is not None:
shiftx = shifty = 0
@@ -247,6 +287,8 @@ class ImageThumbnailsPlugin(Plugin):
config = self.site.config
defaults = { "width": None,
"height": None,
"larger": None,
"smaller": None,
"crop_type": "topleft",
"prefix": 'thumb_'}
if hasattr(config, 'thumbnails'):
@@ -262,17 +304,32 @@ class ImageThumbnailsPlugin(Plugin):
prefix = th.prefix if hasattr(th, 'prefix') else defaults['prefix']
height = th.height if hasattr(th, 'height') else defaults['height']
width = th.width if hasattr(th, 'width') else defaults['width']
larger = th.larger if hasattr(th, 'larger') else defaults['larger']
smaller = th.smaller if hasattr(th, 'smaller') else defaults['smaller']
crop_type = th.crop_type if hasattr(th, 'crop_type') else defaults['crop_type']
if crop_type not in ["topleft", "center", "bottomright"]:
self.logger.error("Unknown crop_type defined for node [%s]" % node)
continue
if width is None and height is None:
self.logger.error("Both width and height are not set for node [%s]" % node)
if width is None and height is None and larger is None and smaller is None:
self.logger.error("At least one of width, height, larger, or smaller must be set for node [%s]" % node)
continue

if ((larger is not None or smaller is not None) and
(width is not None or height is not None)):
self.logger.error("It is not valid to specify both one of width/height and one of larger/smaller for node [%s]" % node)
continue

if larger is None and smaller is None:
preserve_orientation = False
dim1, dim2 = width, height
else:
preserve_orientation = True
dim1, dim2 = larger, smaller

thumbs_list = []
for inc in include:
for path in glob.glob(node.path + os.sep + inc):
thumbs_list.append(path)
for resource in node.resources:
if resource.source_file.kind in ["jpg", "png"] and resource.path in thumbs_list:
self.thumb(resource, width, height, prefix, crop_type)
self.thumb(resource, dim1, dim2, prefix, crop_type, preserve_orientation)

BIN
hyde/tests/ext/images/landscape.jpg View File

Before After
Width: 120  |  Height: 90  |  Size: 377 B

BIN
hyde/tests/ext/images/portrait.jpg View File

Before After
Width: 90  |  Height: 120  |  Size: 377 B

+ 215
- 31
hyde/tests/ext/test_images.py View File

@@ -9,13 +9,21 @@ Requires PIL

from hyde.generator import Generator
from hyde.site import Site
from hyde.ext.plugins.images import thumb_scale_size
import yaml

from fswrap import File

TEST_SITE = File(__file__).parent.parent.child_folder('_test')
IMAGE_SOURCE = File(__file__).parent.child_folder('optipng')
IMAGE_NAME = "hyde-lt-b.png"
IMAGE_SIZE = (538, 132)
IMAGE_SOURCE = File(__file__).parent.child_folder('images')

PORTRAIT_IMAGE = "portrait.jpg"
PORTRAIT_SIZE = (90, 120)
LANDSCAPE_IMAGE = "landscape.jpg"
LANDSCAPE_SIZE = (120, 90)

IMAGES = [PORTRAIT_IMAGE, LANDSCAPE_IMAGE]
SIZES = [PORTRAIT_SIZE, LANDSCAPE_SIZE]

# PIL requirement
import Image
@@ -50,34 +58,34 @@ class TestImageSizer(object):
def test_size_image(self):
text = u"""
<img src="/media/img/%s">
""" % IMAGE_NAME
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text)
assert ' width="%d"' % IMAGE_SIZE[0] in html
assert ' height="%d"' % IMAGE_SIZE[1] in html
assert ' width="%d"' % PORTRAIT_SIZE[0] in html
assert ' height="%d"' % PORTRAIT_SIZE[1] in html

def test_size_image_relative(self):
text = u"""
<img src="media/img/%s">
""" % IMAGE_NAME
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text)
assert ' width="%d"' % IMAGE_SIZE[0] in html
assert ' height="%d"' % IMAGE_SIZE[1] in html
assert ' width="%d"' % PORTRAIT_SIZE[0] in html
assert ' height="%d"' % PORTRAIT_SIZE[1] in html

def test_size_image_no_resize(self):
text = u"""
<img src="/media/img/%s" width="2000" height="150">
""" % IMAGE_NAME
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text)
assert ' width="%d"' % IMAGE_SIZE[0] not in html
assert ' height="%d"' % IMAGE_SIZE[1] not in html
assert ' width="%d"' % PORTRAIT_SIZE[0] not in html
assert ' height="%d"' % PORTRAIT_SIZE[1] not in html

def test_size_image_size_proportional(self):
text = u"""
<img src="/media/img/%s" width="%d">
""" % (IMAGE_NAME, IMAGE_SIZE[0]*2)
""" % (PORTRAIT_IMAGE, PORTRAIT_SIZE[0]*2)
html = self._generic_test_image(text)
assert ' width="%d"' % (IMAGE_SIZE[0]*2) in html
assert ' height="%d"' % (IMAGE_SIZE[1]*2) in html
assert ' width="%d"' % (PORTRAIT_SIZE[0]*2) in html
assert ' height="%d"' % (PORTRAIT_SIZE[1]*2) in html

def test_size_image_not_exists(self):
text = u"""
@@ -87,21 +95,22 @@ class TestImageSizer(object):

def test_size_image_multiline(self):
text = u"""
<img src="/media/img/%s">
""" % IMAGE_NAME
<img
src="/media/img/%s">
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text)
assert ' width="%d"' % IMAGE_SIZE[0] in html
assert ' height="%d"' % IMAGE_SIZE[1] in html
assert ' width="%d"' % PORTRAIT_SIZE[0] in html
assert ' height="%d"' % PORTRAIT_SIZE[1] in html

def test_size_multiple_images(self):
text = u"""
<img src="/media/img/%s">
<img src="/media/img/%s">Hello <img src="/media/img/%s">
<img src="/media/img/%s">Bye
""" % ((IMAGE_NAME,)*4)
""" % ((PORTRAIT_IMAGE,)*4)
html = self._generic_test_image(text)
assert ' width="%d"' % IMAGE_SIZE[0] in html
assert ' height="%d"' % IMAGE_SIZE[1] in html
assert ' width="%d"' % PORTRAIT_SIZE[0] in html
assert ' height="%d"' % PORTRAIT_SIZE[1] in html
assert 'Hello ' in html
assert 'Bye' in html
assert len([f for f in html.split("<img")
@@ -112,24 +121,199 @@ class TestImageSizer(object):
def test_size_malformed1(self):
text = u"""
<img src="/media/img/%s>
""" % IMAGE_NAME
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text)
assert ' width="%d"' % IMAGE_SIZE[0] in html
assert ' height="%d"' % IMAGE_SIZE[1] in html
assert ' width="%d"' % PORTRAIT_SIZE[0] in html
assert ' height="%d"' % PORTRAIT_SIZE[1] in html

def test_size_malformed2(self):
text = u"""
<img src="/media/img/%s alt="hello">
""" % IMAGE_NAME
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text)
assert ' width="%d"' % IMAGE_SIZE[0] in html
assert ' height="%d"' % IMAGE_SIZE[1] in html
assert ' width="%d"' % PORTRAIT_SIZE[0] in html
assert ' height="%d"' % PORTRAIT_SIZE[1] in html

def test_outside_media_url(self):
self.site.config.media_url = "http://media.example.com/"
text = u"""
<img src="http://media.example.com/img/%s" alt="hello">
""" % IMAGE_NAME
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text)
assert ' width="%d"' % IMAGE_SIZE[0] in html
assert ' height="%d"' % IMAGE_SIZE[1] in html
assert ' width="%d"' % PORTRAIT_SIZE[0] in html
assert ' height="%d"' % PORTRAIT_SIZE[1] in html

class TestImageThumbSize(object):

def test_width_only(self):
ow, oh = 100, 200
nw, nh = thumb_scale_size(ow, oh, 50, None)
assert nw == 50
assert nh == 100

def test_width_only_nonintegral(self):
ow, oh = 100, 205
nw, nh = thumb_scale_size(ow, oh, 50, None)
assert nw == 50
assert nh == 103

def test_height_only(self):
ow, oh = 100, 200
nw, nh = thumb_scale_size(ow, oh, None, 100)
assert nw == 50
assert nh == 100

def test_height_only_nonintegral(self):
ow, oh = 105, 200
nw, nh = thumb_scale_size(ow, oh, None, 100)
assert nw == 53
assert nh == 100

def test_height_and_width_portrait(self):
ow, oh = 100, 200
nw, nh = thumb_scale_size(ow, oh, 50, 50)
assert nw == 50
assert nh == 100

def test_height_and_width_landscape(self):
ow, oh = 200, 100
nw, nh = thumb_scale_size(ow, oh, 50, 50)
assert nw == 100
assert nh == 50

def test_larger_only_portrait(self):
ow, oh = 100, 200
nw, nh = thumb_scale_size(ow, oh, 50, None, True)
assert nw == 25
assert nh == 50

def test_larger_only_landscape(self):
ow, oh = 200, 100
nw, nh = thumb_scale_size(ow, oh, 50, None, True)
assert nw == 50
assert nh == 25

def test_smaller_only_portrait(self):
ow, oh = 100, 200
nw, nh = thumb_scale_size(ow, oh, None, 50, True)
assert nw == 50
assert nh == 100

def test_smaller_only_landscape(self):
ow, oh = 200, 100
nw, nh = thumb_scale_size(ow, oh, None, 50, True)
assert nw == 100
assert nh == 50

def test_larger_and_smaller_portrait(self):
ow, oh = 100, 200
nw, nh = thumb_scale_size(ow, oh, 100, 50, True)
assert nw == 50
assert nh == 100

def test_larger_and_smaller_landscape(self):
ow, oh = 200, 100
nw, nh = thumb_scale_size(ow, oh, 100, 50, True)
assert nw == 100
assert nh == 50

class TestImageThumbnails(object):

def setUp(self):
TEST_SITE.make()
TEST_SITE.parent.child_folder(
'sites/test_jinja').copy_contents_to(TEST_SITE)
IMAGES = TEST_SITE.child_folder('content/media/img')
IMAGES.make()
IMAGE_SOURCE.copy_contents_to(IMAGES)
self.image_folder = IMAGES
self.site = Site(TEST_SITE)

def tearDown(self):
TEST_SITE.delete()

def _generate_site_with_meta(self, meta):
self.site.config.mode = "production"
self.site.config.plugins = ['hyde.ext.plugins.meta.MetaPlugin', 'hyde.ext.plugins.images.ImageThumbnailsPlugin']

mlink = File(self.image_folder.child('meta.yaml'))
meta_text = yaml.dump(meta, default_flow_style=False)
mlink.write(meta_text)
gen = Generator(self.site)
gen.generate_all()

def _test_generic_thumbnails(self, meta):
self._generate_site_with_meta(meta)
thumb_meta = meta.get('thumbnails', [])
for th in thumb_meta:
prefix = th.get('prefix')
if prefix is None:
continue

for fn in [PORTRAIT_IMAGE, LANDSCAPE_IMAGE]:
f = File(self._deployed_image(prefix, fn))
assert f.exists

def _deployed_image(self, prefix, filename):
return self.site.config.deploy_root_path.child('media/img/%s%s'%(prefix,filename))

def test_width(self):
prefix='thumb_'
meta = dict(thumbnails=[dict(width=50, prefix=prefix, include=['*.jpg'])])
self._test_generic_thumbnails(meta)
for fn in IMAGES:
im = Image.open(self._deployed_image(prefix, fn))
assert im.size[0] == 50

def test_height(self):
prefix='thumb_'
meta = dict(thumbnails=[dict(height=50, prefix=prefix, include=['*.jpg'])])
self._test_generic_thumbnails(meta)
for fn in IMAGES:
im = Image.open(self._deployed_image(prefix, fn))
assert im.size[1] == 50

def test_width_and_height(self):
prefix='thumb_'
meta = dict(thumbnails=[dict(width=50, height=50, prefix=prefix, include=['*.jpg'])])
self._test_generic_thumbnails(meta)
for fn in IMAGES:
im = Image.open(self._deployed_image(prefix, fn))
assert im.size[0] == 50
assert im.size[1] == 50

def test_larger(self):
prefix='thumb_'
meta = dict(thumbnails=[dict(larger=50, prefix=prefix, include=['*.jpg'])])
self._test_generic_thumbnails(meta)

im = Image.open(self._deployed_image(prefix, PORTRAIT_IMAGE))
assert im.size[1] == 50

im = Image.open(self._deployed_image(prefix, LANDSCAPE_IMAGE))
assert im.size[0] == 50

def test_smaller(self):
prefix='thumb_'
meta = dict(thumbnails=[dict(smaller=50, prefix=prefix, include=['*.jpg'])])
self._test_generic_thumbnails(meta)

im = Image.open(self._deployed_image(prefix, PORTRAIT_IMAGE))
assert im.size[0] == 50

im = Image.open(self._deployed_image(prefix, LANDSCAPE_IMAGE))
assert im.size[1] == 50


def test_larger_and_smaller(self):
prefix='thumb_'
meta = dict(thumbnails=[dict(larger=100, smaller=50, prefix=prefix, include=['*.jpg'])])
self._test_generic_thumbnails(meta)

im = Image.open(self._deployed_image(prefix, PORTRAIT_IMAGE))
assert im.size[0] == 50
assert im.size[1] == 100

im = Image.open(self._deployed_image(prefix, LANDSCAPE_IMAGE))
assert im.size[0] == 100
assert im.size[1] == 50

Loading…
Cancel
Save