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 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): class ImageThumbnailsPlugin(Plugin):
""" """
Provide a function to get thumbnail for any image resource. Provide a function to get thumbnail for any image resource.
@@ -172,20 +210,26 @@ class ImageThumbnailsPlugin(Plugin):
include: include:
- '*.png' - '*.png'
- '*.jpg' - '*.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 and sizes


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


XXX fix docs for larger/smaller

Currently, only supports PNG and JPG. Currently, only supports PNG and JPG.
""" """


def __init__(self, site): def __init__(self, site):
super(ImageThumbnailsPlugin, self).__init__(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 Generate a thumbnail for the given image
""" """
@@ -211,17 +255,13 @@ class ImageThumbnailsPlugin(Plugin):
im = Image.open(resource.path) im = Image.open(resource.path)
if im.mode != 'RGBA': if im.mode != 'RGBA':
im = im.convert('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) im = im.resize((resize_width, resize_height), Image.ANTIALIAS)
if width is not None and height is not None: if width is not None and height is not None:
shiftx = shifty = 0 shiftx = shifty = 0
@@ -247,6 +287,8 @@ class ImageThumbnailsPlugin(Plugin):
config = self.site.config config = self.site.config
defaults = { "width": None, defaults = { "width": None,
"height": None, "height": None,
"larger": None,
"smaller": None,
"crop_type": "topleft", "crop_type": "topleft",
"prefix": 'thumb_'} "prefix": 'thumb_'}
if hasattr(config, 'thumbnails'): if hasattr(config, 'thumbnails'):
@@ -262,17 +304,32 @@ class ImageThumbnailsPlugin(Plugin):
prefix = th.prefix if hasattr(th, 'prefix') else defaults['prefix'] prefix = th.prefix if hasattr(th, 'prefix') else defaults['prefix']
height = th.height if hasattr(th, 'height') else defaults['height'] height = th.height if hasattr(th, 'height') else defaults['height']
width = th.width if hasattr(th, 'width') else defaults['width'] 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'] crop_type = th.crop_type if hasattr(th, 'crop_type') else defaults['crop_type']
if crop_type not in ["topleft", "center", "bottomright"]: if crop_type not in ["topleft", "center", "bottomright"]:
self.logger.error("Unknown crop_type defined for node [%s]" % node) self.logger.error("Unknown crop_type defined for node [%s]" % node)
continue 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 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 = [] thumbs_list = []
for inc in include: for inc in include:
for path in glob.glob(node.path + os.sep + inc): for path in glob.glob(node.path + os.sep + inc):
thumbs_list.append(path) thumbs_list.append(path)
for resource in node.resources: for resource in node.resources:
if resource.source_file.kind in ["jpg", "png"] and resource.path in thumbs_list: 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.generator import Generator
from hyde.site import Site from hyde.site import Site
from hyde.ext.plugins.images import thumb_scale_size
import yaml


from fswrap import File from fswrap import File


TEST_SITE = File(__file__).parent.parent.child_folder('_test') 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 # PIL requirement
import Image import Image
@@ -50,34 +58,34 @@ class TestImageSizer(object):
def test_size_image(self): def test_size_image(self):
text = u""" text = u"""
<img src="/media/img/%s"> <img src="/media/img/%s">
""" % IMAGE_NAME
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text) 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): def test_size_image_relative(self):
text = u""" text = u"""
<img src="media/img/%s"> <img src="media/img/%s">
""" % IMAGE_NAME
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text) 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): def test_size_image_no_resize(self):
text = u""" text = u"""
<img src="/media/img/%s" width="2000" height="150"> <img src="/media/img/%s" width="2000" height="150">
""" % IMAGE_NAME
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text) 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): def test_size_image_size_proportional(self):
text = u""" text = u"""
<img src="/media/img/%s" width="%d"> <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) 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): def test_size_image_not_exists(self):
text = u""" text = u"""
@@ -87,21 +95,22 @@ class TestImageSizer(object):


def test_size_image_multiline(self): def test_size_image_multiline(self):
text = u""" text = u"""
<img src="/media/img/%s">
""" % IMAGE_NAME
<img
src="/media/img/%s">
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text) 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): def test_size_multiple_images(self):
text = u""" text = u"""
<img src="/media/img/%s"> <img src="/media/img/%s">
<img src="/media/img/%s">Hello <img src="/media/img/%s"> <img src="/media/img/%s">Hello <img src="/media/img/%s">
<img src="/media/img/%s">Bye <img src="/media/img/%s">Bye
""" % ((IMAGE_NAME,)*4)
""" % ((PORTRAIT_IMAGE,)*4)
html = self._generic_test_image(text) 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 'Hello ' in html
assert 'Bye' in html assert 'Bye' in html
assert len([f for f in html.split("<img") assert len([f for f in html.split("<img")
@@ -112,24 +121,199 @@ class TestImageSizer(object):
def test_size_malformed1(self): def test_size_malformed1(self):
text = u""" text = u"""
<img src="/media/img/%s> <img src="/media/img/%s>
""" % IMAGE_NAME
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text) 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): def test_size_malformed2(self):
text = u""" text = u"""
<img src="/media/img/%s alt="hello"> <img src="/media/img/%s alt="hello">
""" % IMAGE_NAME
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text) 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): def test_outside_media_url(self):
self.site.config.media_url = "http://media.example.com/" self.site.config.media_url = "http://media.example.com/"
text = u""" text = u"""
<img src="http://media.example.com/img/%s" alt="hello"> <img src="http://media.example.com/img/%s" alt="hello">
""" % IMAGE_NAME
""" % PORTRAIT_IMAGE
html = self._generic_test_image(text) 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