| 1 |
from cStringIO import StringIO |
|---|
| 2 |
|
|---|
| 3 |
from PIL import Image |
|---|
| 4 |
|
|---|
| 5 |
|
|---|
| 6 |
|
|---|
| 7 |
|
|---|
| 8 |
PIL_SCALING_ALGO = Image.ANTIALIAS |
|---|
| 9 |
PIL_QUALITY = 88 |
|---|
| 10 |
MEMBER_IMAGE_SCALE = (75, 100) |
|---|
| 11 |
|
|---|
| 12 |
def scale_image(image_file, max_size=MEMBER_IMAGE_SCALE, |
|---|
| 13 |
default_format = 'PNG'): |
|---|
| 14 |
"""Scales an image down to at most max_size preserving aspect ratio |
|---|
| 15 |
from an input file |
|---|
| 16 |
|
|---|
| 17 |
>>> import Products.CMFPlone |
|---|
| 18 |
>>> import os |
|---|
| 19 |
>>> from StringIO import StringIO |
|---|
| 20 |
>>> from Products.CMFPlone.utils import scale_image |
|---|
| 21 |
>>> from PIL import Image |
|---|
| 22 |
|
|---|
| 23 |
Let's make a couple test images and see how it works (all are |
|---|
| 24 |
100x100), the gif is palletted mode:: |
|---|
| 25 |
|
|---|
| 26 |
>>> plone_path = os.path.dirname(Products.CMFPlone.__file__) |
|---|
| 27 |
>>> pjoin = os.path.join |
|---|
| 28 |
>>> path = pjoin(plone_path, 'tests', 'images') |
|---|
| 29 |
>>> orig_jpg = open(pjoin(path, 'test.jpg')) |
|---|
| 30 |
>>> orig_png = open(pjoin(path, 'test.png')) |
|---|
| 31 |
>>> orig_gif = open(pjoin(path, 'test.gif')) |
|---|
| 32 |
|
|---|
| 33 |
We'll also make some evil non-images, including one which |
|---|
| 34 |
masquerades as a jpeg (which would trick OFS.Image):: |
|---|
| 35 |
|
|---|
| 36 |
>>> invalid = StringIO('<div>Evil!!!</div>') |
|---|
| 37 |
>>> sneaky = StringIO('\377\330<div>Evil!!!</div>') |
|---|
| 38 |
|
|---|
| 39 |
OK, let's get to it, first check that our bad images fail: |
|---|
| 40 |
|
|---|
| 41 |
>>> scale_image(invalid, (50, 50)) |
|---|
| 42 |
Traceback (most recent call last): |
|---|
| 43 |
... |
|---|
| 44 |
IOError: cannot identify image file |
|---|
| 45 |
>>> scale_image(sneaky, (50, 50)) |
|---|
| 46 |
Traceback (most recent call last): |
|---|
| 47 |
... |
|---|
| 48 |
IOError: cannot identify image file |
|---|
| 49 |
|
|---|
| 50 |
Now that that's out of the way we check on our real images to make |
|---|
| 51 |
sure the format and mode are preserved, that they are scaled, and that they |
|---|
| 52 |
return the correct mimetype:: |
|---|
| 53 |
|
|---|
| 54 |
>>> new_jpg, mimetype = scale_image(orig_jpg, (50, 50)) |
|---|
| 55 |
>>> img = Image.open(new_jpg) |
|---|
| 56 |
>>> img.size |
|---|
| 57 |
(50, 50) |
|---|
| 58 |
>>> img.format |
|---|
| 59 |
'JPEG' |
|---|
| 60 |
>>> mimetype |
|---|
| 61 |
'image/jpeg' |
|---|
| 62 |
|
|---|
| 63 |
>>> new_png, mimetype = scale_image(orig_png, (50, 50)) |
|---|
| 64 |
>>> img = Image.open(new_png) |
|---|
| 65 |
>>> img.size |
|---|
| 66 |
(50, 50) |
|---|
| 67 |
>>> img.format |
|---|
| 68 |
'PNG' |
|---|
| 69 |
>>> mimetype |
|---|
| 70 |
'image/png' |
|---|
| 71 |
|
|---|
| 72 |
>>> new_gif, mimetype = scale_image(orig_gif, (50, 50)) |
|---|
| 73 |
>>> img = Image.open(new_gif) |
|---|
| 74 |
>>> img.size |
|---|
| 75 |
(50, 50) |
|---|
| 76 |
>>> img.format |
|---|
| 77 |
'GIF' |
|---|
| 78 |
>>> img.mode |
|---|
| 79 |
'P' |
|---|
| 80 |
>>> mimetype |
|---|
| 81 |
'image/gif' |
|---|
| 82 |
|
|---|
| 83 |
We should also preserve the aspect ratio by scaling to the given |
|---|
| 84 |
width only unless told not to (we need to reset out files before |
|---|
| 85 |
trying again though:: |
|---|
| 86 |
|
|---|
| 87 |
>>> orig_jpg.seek(0) |
|---|
| 88 |
>>> new_jpg, mimetype = scale_image(orig_jpg, (70, 100)) |
|---|
| 89 |
>>> img = Image.open(new_jpg) |
|---|
| 90 |
>>> img.size |
|---|
| 91 |
(70, 70) |
|---|
| 92 |
|
|---|
| 93 |
>>> orig_jpg.seek(0) |
|---|
| 94 |
>>> new_jpg, mimetype = scale_image(orig_jpg, (70, 50)) |
|---|
| 95 |
>>> img = Image.open(new_jpg) |
|---|
| 96 |
>>> img.size |
|---|
| 97 |
(50, 50) |
|---|
| 98 |
|
|---|
| 99 |
""" |
|---|
| 100 |
|
|---|
| 101 |
size = (int(max_size[0]), int(max_size[1])) |
|---|
| 102 |
|
|---|
| 103 |
|
|---|
| 104 |
image = Image.open(image_file) |
|---|
| 105 |
|
|---|
| 106 |
format = image.format |
|---|
| 107 |
mimetype = 'image/%s'%format.lower() |
|---|
| 108 |
cur_size = image.size |
|---|
| 109 |
|
|---|
| 110 |
|
|---|
| 111 |
|
|---|
| 112 |
|
|---|
| 113 |
|
|---|
| 114 |
|
|---|
| 115 |
|
|---|
| 116 |
original_mode = image.mode |
|---|
| 117 |
if original_mode == '1': |
|---|
| 118 |
image = image.convert('L') |
|---|
| 119 |
elif original_mode == 'P': |
|---|
| 120 |
image = image.convert('RGBA') |
|---|
| 121 |
|
|---|
| 122 |
|
|---|
| 123 |
image.thumbnail(size, resample=PIL_SCALING_ALGO) |
|---|
| 124 |
|
|---|
| 125 |
if original_mode == 'P' and format in ('GIF', 'PNG'): |
|---|
| 126 |
image = image.convert('P') |
|---|
| 127 |
|
|---|
| 128 |
new_file = StringIO() |
|---|
| 129 |
image.save(new_file, format, quality=PIL_QUALITY) |
|---|
| 130 |
new_file.seek(0) |
|---|
| 131 |
|
|---|
| 132 |
return new_file, mimetype |
|---|
| 133 |
|
|---|