From df671653f2f6b781c08659372be27d765e7598f1 Mon Sep 17 00:00:00 2001 From: xiongchiamiov Date: Wed, 25 Mar 2015 11:08:18 -0700 Subject: [PATCH] Image previews: smart crop down to 1:2 aspect ratio Per request from the mobile team, image previews should be cropped down to a 1:2 width-to-height ratio as necessary. --- r2/r2/lib/jsontemplates.py | 3 ++- .../lib/providers/image_resizing/__init__.py | 6 +++++- r2/r2/lib/providers/image_resizing/imgix.py | 9 ++++++++- r2/r2/lib/providers/image_resizing/no_op.py | 2 +- .../providers/image_resizing/unsplashit.py | 2 +- .../providers/image_resizing/imgix_test.py | 19 ++++++++++++++++++- 6 files changed, 35 insertions(+), 6 deletions(-) diff --git a/r2/r2/lib/jsontemplates.py b/r2/r2/lib/jsontemplates.py index 4d5efc6bc..90cb1d378 100644 --- a/r2/r2/lib/jsontemplates.py +++ b/r2/r2/lib/jsontemplates.py @@ -581,6 +581,7 @@ class LinkJsonTemplate(ThingJsonTemplate): ) PREVIEW_RESOLUTIONS = (108, 216, 320, 640, 960, 1080) + PREVIEW_MAX_RATIO = 2 def __init__(self): super(LinkJsonTemplate, self).__init__() @@ -650,7 +651,7 @@ class LinkJsonTemplate(ThingJsonTemplate): continue url = g.image_resizing_provider.resize_image( - preview_object, w, censor_nsfw) + preview_object, w, censor_nsfw, self.PREVIEW_MAX_RATIO) h = int(w * source_ratio) preview_resolutions.append({ "url": url, diff --git a/r2/r2/lib/providers/image_resizing/__init__.py b/r2/r2/lib/providers/image_resizing/__init__.py index 3b63c71c1..411f32fef 100644 --- a/r2/r2/lib/providers/image_resizing/__init__.py +++ b/r2/r2/lib/providers/image_resizing/__init__.py @@ -24,7 +24,7 @@ class ImageResizingProvider(object): """Provider for generating resizable image urls. """ - def resize_image(self, image, width=None, censor_nsfw=False): + def resize_image(self, image, width=None, censor_nsfw=False, max_ratio=None): """Turn a url of an image in storage into one that will produce a resized image. @@ -39,6 +39,10 @@ class ImageResizingProvider(object): `censor_nsfw` is a boolean indicating whether the resizer should attempt to censor the image (e.g. by blurring it) due to it being NSFW. + `max_ratio` is the maximum value of the height of the resultant image + divided by the width; if not specified, the aspect ratio will be the + same as the source image. + The return value should be an absolute URL with the `https` scheme if supported, but should also work if accessed with `http`. diff --git a/r2/r2/lib/providers/image_resizing/imgix.py b/r2/r2/lib/providers/image_resizing/imgix.py index 378bb5845..bf1afe6ef 100644 --- a/r2/r2/lib/providers/image_resizing/imgix.py +++ b/r2/r2/lib/providers/image_resizing/imgix.py @@ -39,12 +39,19 @@ class ImgixImageResizingProvider(ImageResizingProvider): ], } - def resize_image(self, image, width=None, censor_nsfw=False): + def resize_image(self, image, width=None, censor_nsfw=False, max_ratio=None): url = UrlParser(image['url']) url.hostname = g.imgix_domain # Let's encourage HTTPS; it's cool, works just fine on HTTP pages, and # will prevent insecure content warnings on HTTPS pages. url.scheme = 'https' + + if max_ratio: + url.update_query(fit='crop') + # http://www.imgix.com/docs/reference/size#param-crop + url.update_query(crop='faces,entropy') + url.update_query(arh=max_ratio) + if width: if width > image['width']: raise NotLargeEnough() diff --git a/r2/r2/lib/providers/image_resizing/no_op.py b/r2/r2/lib/providers/image_resizing/no_op.py index 08839613a..6f0d6ec4c 100644 --- a/r2/r2/lib/providers/image_resizing/no_op.py +++ b/r2/r2/lib/providers/image_resizing/no_op.py @@ -28,6 +28,6 @@ class NoOpImageResizingProvider(ImageResizingProvider): Combines well with the filesystem media provider for an entirely local setup. """ - def resize_image(self, image, width=None, censor_nsfw=False): + def resize_image(self, image, width=None, censor_nsfw=False, max_ratio=None): # The simplest solution: just pass it on through. return image['url'] diff --git a/r2/r2/lib/providers/image_resizing/unsplashit.py b/r2/r2/lib/providers/image_resizing/unsplashit.py index d32292d36..9c4124cdf 100644 --- a/r2/r2/lib/providers/image_resizing/unsplashit.py +++ b/r2/r2/lib/providers/image_resizing/unsplashit.py @@ -28,7 +28,7 @@ class UnsplashitImageResizingProvider(ImageResizingProvider): Useful if you don't want the external dependencies of imgix, but need correctly-sized images for testing a UI. """ - def resize_image(self, image, width=None, censor_nsfw=False): + def resize_image(self, image, width=None, censor_nsfw=False, max_ratio=None): if width is None: width = image['width'] height = width * 2 diff --git a/r2/r2/tests/unit/lib/providers/image_resizing/imgix_test.py b/r2/r2/tests/unit/lib/providers/image_resizing/imgix_test.py index 2f1de5a82..f7d17be9e 100644 --- a/r2/r2/tests/unit/lib/providers/image_resizing/imgix_test.py +++ b/r2/r2/tests/unit/lib/providers/image_resizing/imgix_test.py @@ -33,6 +33,9 @@ from r2.lib.providers.image_resizing.imgix import ImgixImageResizingProvider from r2.lib.utils import UrlParser +URLENCODED_COMMA = '%2C' + + class TestImgixResizer(unittest.TestCase): @classmethod def setUpClass(cls): @@ -66,7 +69,21 @@ class TestImgixResizer(unittest.TestCase): url = self.provider.resize_image(image, width) self.assertEqual(url, 'https://example.com/a.jpg?w=%d' % width) - # TODO: test acceptable aspect ratios per spec! + def test_cropping(self): + image = dict(url='http://s3.amazonaws.com/a.jpg', width=1200, + height=800) + max_ratio = 0.5 + url = self.provider.resize_image(image, max_ratio=max_ratio) + crop = URLENCODED_COMMA.join(('faces', 'entropy')) + self.assertEqual(url, + ('https://example.com/a.jpg?fit=crop&crop=%s&arh=%s' + % (crop, max_ratio))) + + width = 108 + url = self.provider.resize_image(image, width, max_ratio=max_ratio) + self.assertEqual(url, + ('https://example.com/a.jpg?fit=crop&crop=%s&arh=%s&w=%s' + % (crop, max_ratio, width))) def test_sign_url(self): u = UrlParser('http://examples.imgix.net/frog.jpg?w=100')