├── .pylintrc ├── tests ├── __init__.py ├── test-image.jpeg └── tests.py ├── resizeimage ├── __init__.py ├── imageexceptions.py ├── helpers.py └── resizeimage.py ├── setup.cfg ├── pre-commit ├── .gitignore ├── .travis.yml ├── tox.ini ├── LICENSE ├── setup.py ├── README.md └── README.rst /.pylintrc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resizeimage/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | -------------------------------------------------------------------------------- /pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | git-pylint-commit-hook 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | *.pyc 3 | *.egg-info 4 | dist 5 | build 6 | .coverage -------------------------------------------------------------------------------- /tests/test-image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VingtCinq/python-resize-image/HEAD/tests/test-image.jpeg -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - "2.7" 5 | - "3.4" 6 | - "3.5" 7 | - "3.6" 8 | 9 | install: 10 | - pip install -q tox 11 | - pip install -q coveralls 12 | 13 | script: coverage run --source=resizeimage setup.py test 14 | 15 | after_success: 16 | coveralls 17 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist=py27, py34 3 | 4 | [testenv] 5 | deps= 6 | coverage 7 | 8 | setenv= 9 | PYTHONWARNINGS=all 10 | 11 | [testenv:py27] 12 | commands= 13 | coverage run --source resizeimage setup.py test 14 | 15 | [testenv:py34] 16 | commands= 17 | coverage run --source resizeimage setup.py test 18 | -------------------------------------------------------------------------------- /resizeimage/imageexceptions.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class ImageSizeError(Exception): 4 | """ 5 | Raised when the supplied image does not 6 | fit the intial size requirements 7 | """ 8 | def __init__(self, actual_size, required_size): 9 | self.message = 'Image is too small, Image size : %s, Required size : %s' % (actual_size, required_size) 10 | self.actual_size = actual_size 11 | self.required_size = required_size 12 | 13 | def __str__(self): 14 | return repr(self.message) 15 | -------------------------------------------------------------------------------- /resizeimage/helpers.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from cStringIO import StringIO 3 | from PIL import Image 4 | 5 | 6 | def file_to_image(image_file_name): 7 | """ 8 | Convert a file into a Pillow Image object 9 | """ 10 | with open(image_file_name, 'r') as f_image: 11 | return Image.open(f_image) 12 | 13 | 14 | def url_to_image(url): 15 | """ 16 | Fetch an image from url and convert it into a Pillow Image object 17 | """ 18 | r = requests.get(url) 19 | image = StringIO(r.content) 20 | return image 21 | 22 | 23 | def string_to_image(image_string): 24 | """ 25 | Convert string datas into a Pillow Image object 26 | """ 27 | image_filelike = StringIO(image_string) 28 | image = Image.open(image_filelike) 29 | return image 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 vingtcinq.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | from setuptools import setup, find_packages 4 | 5 | 6 | README = os.path.join(os.path.dirname(__file__), 'README.rst') 7 | 8 | # when running tests using tox, README.md is not found 9 | try: 10 | with open(README) as file: 11 | long_description = file.read() 12 | except Exception: 13 | long_description = '' 14 | 15 | 16 | setup( 17 | name='python-resize-image', 18 | version='1.1.20', 19 | description='A Small python package to easily resize images', 20 | long_description=long_description, 21 | url='https://github.com/VingtCinq/python-resize-image', 22 | author='Charles TISSIER', 23 | author_email='charles@vingtcinq.io', 24 | license='MIT', 25 | classifiers=[ 26 | 'Development Status :: 5 - Production/Stable', 27 | 'Intended Audience :: Developers', 28 | 'Topic :: Software Development :: Libraries :: Python Modules', 29 | 'License :: OSI Approved :: MIT License', 30 | 'Programming Language :: Python :: 2.7', 31 | 'Programming Language :: Python :: 3', 32 | 'Programming Language :: Python :: 3.4', 33 | 'Programming Language :: Python :: 3.5', 34 | 'Programming Language :: Python :: 3.6', 35 | ], 36 | keywords='image resize resizing python', 37 | packages=find_packages(), 38 | install_requires=['Pillow>=5.1.0', 'requests>=2.19.1'], 39 | test_suite='tests', 40 | ) 41 | -------------------------------------------------------------------------------- /resizeimage/resizeimage.py: -------------------------------------------------------------------------------- 1 | """main module with resize and validation functions""" 2 | from __future__ import division 3 | import math 4 | import sys 5 | from functools import wraps 6 | 7 | from PIL import Image 8 | from .imageexceptions import ImageSizeError 9 | 10 | 11 | def validate(validator): 12 | """ 13 | Return a decorator that validates arguments with provided `validator` 14 | function. 15 | 16 | This will also store the validator function as `func.validate`. 17 | The decorator returned by this function, can bypass the validator 18 | if `validate=False` is passed as argument otherwise the fucntion is 19 | called directly. 20 | 21 | The validator must raise an exception, if the function can not 22 | be called. 23 | """ 24 | 25 | def decorator(func): 26 | """Bound decorator to a particular validator function""" 27 | 28 | @wraps(func) 29 | def wrapper(image, size, validate=True, *args, **kwargs): 30 | if validate: 31 | validator(image, size) 32 | return func(image, size, *args, **kwargs) 33 | return wrapper 34 | 35 | return decorator 36 | 37 | 38 | def _is_big_enough(image, size): 39 | """Check that the image's size superior to `size`""" 40 | if (size[0] > image.size[0]) and (size[1] > image.size[1]): 41 | raise ImageSizeError(image.size, size) 42 | 43 | 44 | def _width_is_big_enough(image, width): 45 | """Check that the image width is superior to `width`""" 46 | if width > image.size[0]: 47 | raise ImageSizeError(image.size[0], width) 48 | 49 | 50 | def _height_is_big_enough(image, height): 51 | """Check that the image height is superior to `height`""" 52 | if height > image.size[1]: 53 | raise ImageSizeError(image.size[1], height) 54 | 55 | 56 | @validate(_is_big_enough) 57 | def resize_crop(image, size): 58 | """ 59 | Crop the image with a centered rectangle of the specified size 60 | image: a Pillow image instance 61 | size: a list of two integers [width, height] 62 | """ 63 | img_format = image.format 64 | image = image.copy() 65 | old_size = image.size 66 | left = (old_size[0] - size[0]) / 2 67 | top = (old_size[1] - size[1]) / 2 68 | right = old_size[0] - left 69 | bottom = old_size[1] - top 70 | rect = [int(math.ceil(x)) for x in (left, top, right, bottom)] 71 | left, top, right, bottom = rect 72 | crop = image.crop((left, top, right, bottom)) 73 | crop.format = img_format 74 | return crop 75 | 76 | 77 | @validate(_is_big_enough) 78 | def resize_cover(image, size, resample=Image.LANCZOS): 79 | """ 80 | Resize image according to size. 81 | image: a Pillow image instance 82 | size: a list of two integers [width, height] 83 | """ 84 | img_format = image.format 85 | img = image.copy() 86 | img_size = img.size 87 | ratio = max(size[0] / img_size[0], size[1] / img_size[1]) 88 | new_size = [ 89 | int(math.ceil(img_size[0] * ratio)), 90 | int(math.ceil(img_size[1] * ratio)) 91 | ] 92 | img = img.resize((new_size[0], new_size[1]), resample) 93 | img = resize_crop(img, size) 94 | img.format = img_format 95 | return img 96 | 97 | 98 | def resize_contain(image, size, resample=Image.LANCZOS, bg_color=(255, 255, 255, 0)): 99 | """ 100 | Resize image according to size. 101 | image: a Pillow image instance 102 | size: a list of two integers [width, height] 103 | """ 104 | img_format = image.format 105 | img = image.copy() 106 | img.thumbnail((size[0], size[1]), resample) 107 | background = Image.new('RGBA', (size[0], size[1]), bg_color) 108 | img_position = ( 109 | int(math.ceil((size[0] - img.size[0]) / 2)), 110 | int(math.ceil((size[1] - img.size[1]) / 2)) 111 | ) 112 | background.paste(img, img_position) 113 | background.format = img_format 114 | return background.convert('RGBA') 115 | 116 | 117 | @validate(_width_is_big_enough) 118 | def resize_width(image, size, resample=Image.LANCZOS): 119 | """ 120 | Resize image according to size. 121 | image: a Pillow image instance 122 | size: an integer or a list or tuple of two integers [width, height] 123 | """ 124 | try: 125 | width = size[0] 126 | except: 127 | width = size 128 | img_format = image.format 129 | img = image.copy() 130 | img_size = img.size 131 | # If the origial image has already the good width, return it 132 | # fix issue #16 133 | if img_size[0] == width: 134 | return image 135 | new_height = int(math.ceil((width / img_size[0]) * img_size[1])) 136 | img.thumbnail((width, new_height), resample) 137 | img.format = img_format 138 | return img 139 | 140 | 141 | @validate(_height_is_big_enough) 142 | def resize_height(image, size, resample=Image.LANCZOS): 143 | """ 144 | Resize image according to size. 145 | image: a Pillow image instance 146 | size: an integer or a list or tuple of two integers [width, height] 147 | """ 148 | try: 149 | height = size[1] 150 | except: 151 | height = size 152 | img_format = image.format 153 | img = image.copy() 154 | img_size = img.size 155 | # If the origial image has already the good height, return it 156 | # fix issue #16 157 | if img_size[1] == height: 158 | return image 159 | new_width = int(math.ceil((height / img_size[1]) * img_size[0])) 160 | img.thumbnail((new_width, height), resample) 161 | img.format = img_format 162 | return img 163 | 164 | 165 | def resize_thumbnail(image, size, resample=Image.LANCZOS): 166 | """ 167 | Resize image according to size. 168 | image: a Pillow image instance 169 | size: a list of two integers [width, height] 170 | """ 171 | 172 | img_format = image.format 173 | img = image.copy() 174 | img.thumbnail((size[0], size[1]), resample) 175 | img.format = img_format 176 | return img 177 | 178 | 179 | def resize(method, *args, **kwargs): 180 | """ 181 | Helper function to access one of the resize function. 182 | method: one among 'crop', 'cover', 'contain', 'width', 'height' or 'thumbnail' 183 | image: a Pillow image instance 184 | size: a list or tuple of two integers [width, height] 185 | """ 186 | if method not in ['crop', 187 | 'cover', 188 | 'contain', 189 | 'width', 190 | 'height', 191 | 'thumbnail']: 192 | raise ValueError(u"method argument should be one of \ 193 | 'crop', 'cover', 'contain', 'width', 'height' or 'thumbnail'") 194 | return getattr(sys.modules[__name__], 'resize_%s' % method)(*args, **kwargs) 195 | -------------------------------------------------------------------------------- /tests/tests.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import unittest 4 | from contextlib import contextmanager 5 | 6 | from PIL import Image 7 | 8 | from resizeimage import resizeimage 9 | from resizeimage.imageexceptions import ImageSizeError 10 | 11 | 12 | class TestValidateDecorator(unittest.TestCase): 13 | 14 | def validation(x, y): 15 | if x < y: 16 | raise Exception() 17 | else: 18 | return True 19 | 20 | @staticmethod 21 | @resizeimage.validate(validation) 22 | def func(x, y): 23 | return x * y 24 | 25 | def test_no_exception(self): 26 | """ 27 | Test that when the validate function does not raise an 28 | error, the correct result is returned. 29 | """ 30 | self.assertEqual(self.func(42, 2), 84) 31 | 32 | def test_exception(self): 33 | """ 34 | Test that when the validate fails, the exception is 35 | properly propagated. 36 | """ 37 | with self.assertRaises(Exception): 38 | self.func(2, 42) 39 | 40 | def test_no_validation(self): 41 | """ 42 | Test that when the validate fails, the exception is 43 | properly propagated. 44 | """ 45 | self.assertEqual(self.func(2, 42, validate=False), 84) 46 | 47 | def test_validation_only_no_exception(self): 48 | """ 49 | Test that when the validate is called directly it returns 50 | `True` 51 | """ 52 | def validate(x): 53 | if x < 0: 54 | raise Exception() 55 | else: 56 | return True 57 | 58 | 59 | class TestResizeimage(unittest.TestCase): 60 | """ 61 | Run tests for all functions 62 | the given image for testing is 800x533 63 | """ 64 | 65 | @classmethod 66 | def setUpClass(self): 67 | """ 68 | Setup a temporary directory to store image 69 | """ 70 | path = os.path.dirname(__file__) 71 | self.test_image_filepath = os.path.join(path, "test-image.jpeg") 72 | tmpname = 'tmp-images' 73 | self._tmp_dir = os.path.join(path, tmpname) 74 | if os.path.isdir(self._tmp_dir): 75 | shutil.rmtree(self._tmp_dir) 76 | os.makedirs(self._tmp_dir) 77 | 78 | def _tmp_filename(self, filename): 79 | """ 80 | Get relative path for the given filename 81 | """ 82 | return os.path.join(self._tmp_dir, filename) 83 | 84 | @contextmanager 85 | def _open_test_image(self): 86 | with open(self.test_image_filepath, 'r+b') as f: 87 | image = Image.open(f) 88 | yield image 89 | 90 | def test_resize_crop(self): 91 | """ 92 | Test that the image resized with resize_crop 93 | has the expected size 94 | """ 95 | with self._open_test_image() as img: 96 | img = resizeimage.resize_crop(img, [200, 200]) 97 | filename = self._tmp_filename('crop.jpeg') 98 | img.save(filename, img.format) 99 | with Image.open(filename) as image: 100 | self.assertEqual(image.size, (200, 200)) 101 | 102 | def test_can_not_resize_crop_larger_size(self): 103 | """ 104 | Test that resizing an image with resize_crop 105 | to a size larger than the original raises an error 106 | """ 107 | with self._open_test_image() as img: 108 | with self.assertRaises(ImageSizeError): 109 | resizeimage.resize_crop(img, (801, 534)) 110 | 111 | def test_resize_cover(self): 112 | """ 113 | Test that the image resized with resize_cover 114 | has the expected size 115 | """ 116 | with self._open_test_image() as img: 117 | img = resizeimage.resize_cover(img, [200, 100]) 118 | filename = self._tmp_filename('resize-cover.jpeg') 119 | img.save(filename, img.format) 120 | with Image.open(filename) as image: 121 | self.assertEqual(image.size, (200, 100)) 122 | 123 | def test_can_not_resize_cover_larger_size(self): 124 | """ 125 | Test that resizing an image with resize_cover 126 | to a size larger than the original raises an error 127 | """ 128 | with self._open_test_image() as img: 129 | with self.assertRaises(ImageSizeError): 130 | resizeimage.resize_cover(img, (801, 534)) 131 | 132 | def test_resize_contain(self): 133 | """ 134 | Test that the image resized with resize_contain 135 | has the expected size 136 | """ 137 | with self._open_test_image() as img: 138 | img = resizeimage.resize_contain(img, [200, 100]) 139 | filename = self._tmp_filename('resize-contain.jpeg') 140 | img.save(filename, img.format) 141 | with Image.open(filename) as image: 142 | self.assertEqual(image.size, (200, 100)) 143 | 144 | def test_resize_contain_larger_size(self): 145 | """ 146 | Test that the image resized with resize_contain 147 | has the expected size 148 | """ 149 | with self._open_test_image() as img: 150 | img = resizeimage.resize_contain(img, [801, 534]) 151 | filename = self._tmp_filename('resize-contain-larger.jpeg') 152 | img.save(filename, img.format) 153 | with Image.open(filename) as image: 154 | self.assertEqual(image.size, (801, 534)) 155 | 156 | def test_resize_width(self): 157 | """ 158 | Test that the image resized with resize_width 159 | has the expected size 160 | """ 161 | with self._open_test_image() as img: 162 | img = resizeimage.resize_width(img, 200) 163 | filename = self._tmp_filename('resize-width.jpeg') 164 | img.save(filename, img.format) 165 | with Image.open(filename) as image: 166 | self.assertEqual(image.size[0], 200) 167 | 168 | def test_can_not_resize_larger_width(self): 169 | """ 170 | Test that resizing an image with resize_width 171 | to a size larger than the original raises an error 172 | """ 173 | with self._open_test_image() as img: 174 | with self.assertRaises(ImageSizeError): 175 | resizeimage.resize_width(img, 801) 176 | 177 | def test_resize_height(self): 178 | """ 179 | Test that the image resized with resize_height 180 | has the expected size 181 | """ 182 | with self._open_test_image() as img: 183 | img = resizeimage.resize_height(img, 200) 184 | filename = self._tmp_filename('resize-height.jpeg') 185 | img.save(filename, img.format) 186 | with Image.open(filename) as image: 187 | self.assertEqual(image.size[1], 200) 188 | 189 | def test_can_not_resize_larger_height(self): 190 | with self._open_test_image() as img: 191 | with self.assertRaises(ImageSizeError): 192 | resizeimage.resize_height(img, 534) 193 | 194 | def test_resize_thumbnail(self): 195 | """ 196 | Test that the image resized with resize_thumbnail 197 | has the expected size 198 | """ 199 | with self._open_test_image() as img: 200 | img = resizeimage.resize_thumbnail(img, [200, 200]) 201 | filename = self._tmp_filename('resize-thumbnail.jpeg') 202 | img.save(filename, img.format) 203 | with Image.open(filename) as image: 204 | self.assertEqual(image.size, (200, 133)) 205 | 206 | 207 | if __name__ == '__main__': 208 | suite = unittest.TestLoader().loadTestsFromTestCase(TestResizeimage) 209 | unittest.TextTestRunner(verbosity=2).run(suite) 210 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![python-resize-image v1.1.20 on PyPi](https://img.shields.io/badge/pypi-1.1.20-green.svg)](https://pypi.python.org/pypi/python-resize-image) 2 | ![MIT license](https://img.shields.io/badge/licence-MIT-blue.svg) 3 | ![Stable](https://img.shields.io/badge/status-stable-green.svg) 4 | 5 | # A python package to easily resize images 6 | 7 | 8 | This package provides function for easily resizing images. 9 | 10 | ## Dependencies 11 | 12 | - Pillow 2.7++ 13 | - Python 2.7/3.4 14 | 15 | ## Introduction 16 | 17 | The following functions are supported: 18 | 19 | * `resize_crop` crop the image with a centered rectangle of the specified size. 20 | * `resize_cover` resize the image to fill the specified area, crop as needed (same behavior as `background-size: cover`). 21 | * `resize_contain` resize the image so that it can fit in the specified area, keeping the ratio and without crop (same behavior as `background-size: contain`). 22 | * `resize_height` resize the image to the specified height adjusting width to keep the ratio the same. 23 | * `resize_width` resize the image to the specified width adjusting height to keep the ratio the same. 24 | * `resize_thumbnail` resize image while keeping the ratio trying its best to match the specified size. 25 | 26 | 27 | 28 | ## Installation 29 | 30 | 31 | Install python-resize-image using pip: 32 | 33 | ``` 34 | pip install python-resize-image 35 | ``` 36 | 37 | 38 | ## Usage 39 | 40 | 41 | python-resize-image takes as first argument a `PIL.Image` and then `size` argument which can be a single integer or tuple of two integers. 42 | 43 | In the following example, we open an image, *crop* it and save as new file: 44 | 45 | ```python 46 | from PIL import Image 47 | 48 | from resizeimage import resizeimage 49 | 50 | 51 | with open('test-image.jpeg', 'r+b') as f: 52 | with Image.open(f) as image: 53 | cover = resizeimage.resize_cover(image, [200, 100]) 54 | cover.save('test-image-cover.jpeg', image.format) 55 | ``` 56 | 57 | Before resizing, python-image-resize will check whether the operation can be done. A resize is considered valid if it doesn't require to increase 58 | one of the dimension. To avoid the test add `validate=False` as argument: 59 | 60 | ```python 61 | cover = resizeimage.resize_cover(image, [200, 100], validate=False) 62 | ``` 63 | 64 | You can also create a two step process validation then processing using `validate` function attached to resized function which allows to test the viability of the resize without doing it just after validation. `validate` is available using the dot `.` operator on every resize function e.g. `resize_cover.validate`. 65 | 66 | The first exemple is rewritten in the following snippet to use this feature: 67 | 68 | ```python 69 | from PIL import Image 70 | 71 | from resizeimage import resizeimage 72 | 73 | with open('test-image.jpeg', 'r+b') 74 | with Image.open() as image: 75 | is_valid = resizeimage.resize_cover.validate(image, [200, 100]) 76 | 77 | # do something else... 78 | 79 | if is_valid: 80 | with Image.open('test-image.jpeg') as image: 81 | resizeimage.resize_cover.validate(image, [200, 100], validate=False) 82 | cover = resizeimage.resize_cover(image, [200, 100]) 83 | cover.save('test-image-cover.jpeg', image.format) 84 | ``` 85 | 86 | Mind the fact that it's useless to validate the image twice, so we pass `validate=False` to `resize_cover.validate`. 87 | 88 | ## API Reference 89 | 90 | ### `resize_crop(image, size, validate=True)` 91 | 92 | Crop the image with a centered rectangle of the specified size. 93 | 94 | Crop an image with a 200x200 cented square: 95 | 96 | ```python 97 | from PIL import Image 98 | from resizeimage import resizeimage 99 | 100 | fd_img = open('test-image.jpeg', 'r') 101 | img = Image.open(fd_img) 102 | img = resizeimage.resize_crop(img, [200, 200]) 103 | img.save('test-image-crop.jpeg', img.format) 104 | fd_img.close() 105 | ``` 106 | 107 | ### `resize_cover(image, size, validate=True, resample=Image.LANCZOS)` 108 | 109 | Resize the image to fill the specified area, crop as needed. It's the same behavior as css `background-size: cover` property. 110 | 111 | Resize and crop (from center) the image so that it covers a 200x100 rectangle. 112 | 113 | ```python 114 | from PIL import Image 115 | from resizeimage import resizeimage 116 | 117 | fd_img = open('test-image.jpeg', 'r') 118 | img = Image.open(fd_img) 119 | img = resizeimage.resize_cover(img, [200, 100]) 120 | img.save('test-image-cover.jpeg', img.format) 121 | fd_img.close() 122 | ``` 123 | 124 | ### `resize_contain(image, size, validate=True, resample=Image.LANCZOS, bg_color=(255, 255, 255, 0))` 125 | 126 | Resize the image so that it can fit in the specified area, keeping the ratio and without crop. It's the same behavior as css `background-size: contain` property. A white a background border is created. 127 | 128 | Resize the image to minimum so that it is contained in a 200x100 rectangle is the ratio between source and destination image. 129 | 130 | ```python 131 | from PIL import Image 132 | from resizeimage import resizeimage 133 | 134 | fd_img = open('test-image.jpeg', 'r') 135 | img = Image.open(fd_img) 136 | img = resizeimage.resize_contain(img, [200, 100]) 137 | img.save('test-image-contain.jpeg', img.format) 138 | fd_img.close() 139 | ``` 140 | 141 | ### `resize_width(image, width, validate=True, resample=Image.LANCZOS)` 142 | 143 | Resize the image to the specified height adjusting width to keep the ratio the same. 144 | 145 | Resize the image to be 200px width: 146 | 147 | ```python 148 | from PIL import Image 149 | from resizeimage import resizeimage 150 | 151 | fd_img = open('test-image.jpeg', 'r') 152 | img = Image.open(fd_img) 153 | img = resizeimage.resize_width(img, 200) 154 | img.save('test-image-width.jpeg', img.format) 155 | fd_img.close() 156 | ``` 157 | 158 | ### `resize_height(image, height, validate=True, resample=Image.LANCZOS)` 159 | 160 | Resize the image to the specified width adjusting height to keep the ratio the same. 161 | 162 | Resize the image to be 200px height: 163 | 164 | ```python 165 | from PIL import Image 166 | from resizeimage import resizeimage 167 | 168 | fd_img = open('test-image.jpeg', 'r') 169 | img = Image.open(fd_img) 170 | img = resizeimage.resize_height(img, 200) 171 | img.save('test-image-height.jpeg', img.format) 172 | fd_img.close() 173 | ``` 174 | 175 | ### `resize_thumbnail(image, size, validate=True, resample=Image.LANCZOS)` 176 | 177 | Resize image while keeping the ratio trying its best to match the specified size. 178 | 179 | Resize the image to be contained in a 200px square: 180 | 181 | ```python 182 | from PIL import Image 183 | from resizeimage import resizeimage 184 | 185 | fd_img = open('test-image.jpeg', 'r') 186 | img = Image.open(fd_img) 187 | img = resizeimage.resize_thumbnail(img, [200, 200]) 188 | img.save('test-image-thumbnail.jpeg', img.format) 189 | fd_img.close() 190 | ``` 191 | 192 | ### `resize(method, *args, **kwargs)` 193 | 194 | Resize Image with the specified method : 'crop', 'cover', 'contain', 'width', 'height' or 'thumbnail'. 195 | 196 | 197 | ```python 198 | from PIL import Image 199 | from resizeimage import resizeimage 200 | 201 | fd_img = open('test-image.jpeg', 'r') 202 | img = Image.open(fd_img) 203 | img = resizeimage.resize('thumbnail', img, [200, 200]) 204 | img.save('test-image-thumbnail.jpeg', img.format) 205 | fd_img.close() 206 | ``` 207 | 208 | ## Tests 209 | 210 | ``` 211 | pip install -r requirements.dev.txt 212 | pip install -e . 213 | python setup.py test 214 | ``` 215 | 216 | ## Contribute 217 | 218 | python-resize-image is hosted at [github.com/VingtCinq/python-resize-image/](https://github.com/VingtCinq/python-resize-image). 219 | 220 | Before coding install `pre-commit` as git hook using the following command: 221 | 222 | ``` 223 | cp pre-commit .git/hooks/ 224 | ``` 225 | 226 | And install the hook and pylint: 227 | 228 | ``` 229 | pip install git-pylint-commit-hook pylint 230 | ``` 231 | 232 | If you want to force a commit (you need a good reason to do that) use `commit` with the `-n` option e.g. `git commit -n`. 233 | 234 | 235 | ## Support 236 | 237 | If you are having issues, please let us know. 238 | 239 | ## License 240 | 241 | The project is licensed under the MIT License. 242 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | |python-resize-image v1.1.20 on PyPi| |MIT license| |Stable| 2 | 3 | A python package to easily resize images 4 | ======================================== 5 | 6 | This package provides function for easily resizing images. 7 | 8 | Dependencies 9 | ------------ 10 | 11 | - Pillow 2.7++ 12 | - Python 2.7/3.4 13 | 14 | Introduction 15 | ------------ 16 | 17 | The following functions are supported: 18 | 19 | - ``resize_crop`` crop the image with a centered rectangle of the 20 | specified size. 21 | - ``resize_cover`` resize the image to fill the specified area, crop as 22 | needed (same behavior as ``background-size: cover``). 23 | - ``resize_contain`` resize the image so that it can fit in the 24 | specified area, keeping the ratio and without crop (same behavior as 25 | ``background-size: contain``). 26 | - ``resize_height`` resize the image to the specified height adjusting 27 | width to keep the ratio the same. 28 | - ``resize_width`` resize the image to the specified width adjusting 29 | height to keep the ratio the same. 30 | - ``resize_thumbnail`` resize image while keeping the ratio trying its 31 | best to match the specified size. 32 | 33 | Installation 34 | ------------ 35 | 36 | Install python-resize-image using pip: 37 | 38 | :: 39 | 40 | pip install python-resize-image 41 | 42 | Usage 43 | ----- 44 | 45 | python-resize-image takes as first argument a ``PIL.Image`` and then 46 | ``size`` argument which can be a single integer or tuple of two 47 | integers. 48 | 49 | In the following example, we open an image, *crop* it and save as new 50 | file: 51 | 52 | .. code:: python 53 | 54 | from PIL import Image 55 | 56 | from resizeimage import resizeimage 57 | 58 | 59 | with open('test-image.jpeg', 'r+b') as f: 60 | with Image.open(f) as image: 61 | cover = resizeimage.resize_cover(image, [200, 100]) 62 | cover.save('test-image-cover.jpeg', image.format) 63 | 64 | Before resizing, python-image-resize will check whether the operation 65 | can be done. A resize is considered valid if it doesn’t require to 66 | increase one of the dimension. To avoid the test add ``validate=False`` 67 | as argument: 68 | 69 | .. code:: python 70 | 71 | cover = resizeimage.resize_cover(image, [200, 100], validate=False) 72 | 73 | You can also create a two step process validation then processing using 74 | ``validate`` function attached to resized function which allows to test 75 | the viability of the resize without doing it just after validation. 76 | ``validate`` is available using the dot ``.`` operator on every resize 77 | function e.g. ``resize_cover.validate``. 78 | 79 | The first exemple is rewritten in the following snippet to use this 80 | feature: 81 | 82 | .. code:: python 83 | 84 | from PIL import Image 85 | 86 | from resizeimage import resizeimage 87 | 88 | with open('test-image.jpeg', 'r+b') 89 | with Image.open() as image: 90 | is_valid = resizeimage.resize_cover.validate(image, [200, 100]) 91 | 92 | # do something else... 93 | 94 | if is_valid: 95 | with Image.open('test-image.jpeg') as image: 96 | resizeimage.resize_cover.validate(image, [200, 100], validate=False) 97 | cover = resizeimage.resize_cover(image, [200, 100]) 98 | cover.save('test-image-cover.jpeg', image.format) 99 | 100 | Mind the fact that it’s useless to validate the image twice, so we pass 101 | ``validate=False`` to ``resize_cover.validate``. 102 | 103 | API Reference 104 | ------------- 105 | 106 | ``resize_crop(image, size, validate=True)`` 107 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 108 | 109 | Crop the image with a centered rectangle of the specified size. 110 | 111 | Crop an image with a 200x200 cented square: 112 | 113 | .. code:: python 114 | 115 | from PIL import Image 116 | from resizeimage import resizeimage 117 | 118 | fd_img = open('test-image.jpeg', 'r') 119 | img = Image.open(fd_img) 120 | img = resizeimage.resize_crop(img, [200, 200]) 121 | img.save('test-image-crop.jpeg', img.format) 122 | fd_img.close() 123 | 124 | ``resize_cover(image, size, validate=True, resample=Image.LANCZOS)`` 125 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 126 | 127 | Resize the image to fill the specified area, crop as needed. It’s the 128 | same behavior as css ``background-size: cover`` property. 129 | 130 | Resize and crop (from center) the image so that it covers a 200x100 131 | rectangle. 132 | 133 | .. code:: python 134 | 135 | from PIL import Image 136 | from resizeimage import resizeimage 137 | 138 | fd_img = open('test-image.jpeg', 'r') 139 | img = Image.open(fd_img) 140 | img = resizeimage.resize_cover(img, [200, 100]) 141 | img.save('test-image-cover.jpeg', img.format) 142 | fd_img.close() 143 | 144 | ``resize_contain(image, size, validate=True, resample=Image.LANCZOS, bg_color=(255, 255, 255, 0))`` 145 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 146 | 147 | Resize the image so that it can fit in the specified area, keeping the 148 | ratio and without crop. It’s the same behavior as css 149 | ``background-size: contain`` property. A white a background border is 150 | created. 151 | 152 | Resize the image to minimum so that it is contained in a 200x100 153 | rectangle is the ratio between source and destination image. 154 | 155 | .. code:: python 156 | 157 | from PIL import Image 158 | from resizeimage import resizeimage 159 | 160 | fd_img = open('test-image.jpeg', 'r') 161 | img = Image.open(fd_img) 162 | img = resizeimage.resize_contain(img, [200, 100]) 163 | img.save('test-image-contain.jpeg', img.format) 164 | fd_img.close() 165 | 166 | ``resize_width(image, width, validate=True, resample=Image.LANCZOS)`` 167 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 168 | 169 | Resize the image to the specified height adjusting width to keep the 170 | ratio the same. 171 | 172 | Resize the image to be 200px width: 173 | 174 | .. code:: python 175 | 176 | from PIL import Image 177 | from resizeimage import resizeimage 178 | 179 | fd_img = open('test-image.jpeg', 'r') 180 | img = Image.open(fd_img) 181 | img = resizeimage.resize_width(img, 200) 182 | img.save('test-image-width.jpeg', img.format) 183 | fd_img.close() 184 | 185 | ``resize_height(image, height, validate=True, resample=Image.LANCZOS)`` 186 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 187 | 188 | Resize the image to the specified width adjusting height to keep the 189 | ratio the same. 190 | 191 | Resize the image to be 200px height: 192 | 193 | .. code:: python 194 | 195 | from PIL import Image 196 | from resizeimage import resizeimage 197 | 198 | fd_img = open('test-image.jpeg', 'r') 199 | img = Image.open(fd_img) 200 | img = resizeimage.resize_height(img, 200) 201 | img.save('test-image-height.jpeg', img.format) 202 | fd_img.close() 203 | 204 | ``resize_thumbnail(image, size, validate=True, resample=Image.LANCZOS)`` 205 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 206 | 207 | Resize image while keeping the ratio trying its best to match the 208 | specified size. 209 | 210 | Resize the image to be contained in a 200px square: 211 | 212 | .. code:: python 213 | 214 | from PIL import Image 215 | from resizeimage import resizeimage 216 | 217 | fd_img = open('test-image.jpeg', 'r') 218 | img = Image.open(fd_img) 219 | img = resizeimage.resize_thumbnail(img, [200, 200]) 220 | img.save('test-image-thumbnail.jpeg', img.format) 221 | fd_img.close() 222 | 223 | ``resize(method, *args, **kwargs)`` 224 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 225 | 226 | Resize Image with the specified method : ‘crop’, ‘cover’, ‘contain’, 227 | ‘width’, ‘height’ or ‘thumbnail’. 228 | 229 | .. code:: python 230 | 231 | from PIL import Image 232 | from resizeimage import resizeimage 233 | 234 | fd_img = open('test-image.jpeg', 'r') 235 | img = Image.open(fd_img) 236 | img = resizeimage.resize('thumbnail', img, [200, 200]) 237 | img.save('test-image-thumbnail.jpeg', img.format) 238 | fd_img.close() 239 | 240 | Tests 241 | ----- 242 | 243 | :: 244 | 245 | pip install -r requirements.dev.txt 246 | pip install -e . 247 | python setup.py test 248 | 249 | Contribute 250 | ---------- 251 | 252 | python-resize-image is hosted at 253 | `github.com/VingtCinq/python-resize-image/ `__. 254 | 255 | Before coding install ``pre-commit`` as git hook using the following 256 | command: 257 | 258 | :: 259 | 260 | cp pre-commit .git/hooks/ 261 | 262 | And install the hook and pylint: 263 | 264 | :: 265 | 266 | pip install git-pylint-commit-hook pylint 267 | 268 | If you want to force a commit (you need a good reason to do that) use 269 | ``commit`` with the ``-n`` option e.g. ``git commit -n``. 270 | 271 | Support 272 | ------- 273 | 274 | If you are having issues, please let us know. 275 | 276 | License 277 | ------- 278 | 279 | The project is licensed under the MIT License. 280 | 281 | .. |python-resize-image v1.1.20 on PyPi| image:: https://img.shields.io/badge/pypi-1.1.20-green.svg 282 | :target: https://pypi.python.org/pypi/python-resize-image 283 | .. |MIT license| image:: https://img.shields.io/badge/licence-MIT-blue.svg 284 | .. |Stable| image:: https://img.shields.io/badge/status-stable-green.svg 285 | 286 | --------------------------------------------------------------------------------