diff --git a/sigal/gallery.py b/sigal/gallery.py index ca4a6bf..4db8bcc 100644 --- a/sigal/gallery.py +++ b/sigal/gallery.py @@ -33,7 +33,7 @@ from multiprocessing import Pool from os.path import join from PIL import Image as PILImage -from .image import Image +from .image import generate_image, generate_thumbnail from .settings import get_thumb from .writer import Writer @@ -211,7 +211,6 @@ class Gallery(object): for f in img_iterator: filename = os.path.split(f)[1] outname = join(outpath, filename) - self.logger.info(filename) if os.path.isfile(outname) and not self.force: self.logger.info("%s exists - skipping", filename) @@ -239,25 +238,27 @@ def process_image(filepath, outpath, settings): filename = os.path.split(filepath)[1] outname = join(outpath, filename) + ext = os.path.splitext(filename) - img = Image(filepath) + if ext in ['.jpg', '.jpeg', '.JPG', '.JPEG']: + options = settings['jpg_options'] + elif ext == '.png': + options = {'optimize': True} + else: + options = {} - if settings['keep_orig']: - img.save(join(outpath, settings['orig_dir'], filename), - options=settings['jpg_options']) + # TODO + # if settings['keep_orig']: + # img.save(join(outpath, settings['orig_dir'], filename), + # options=settings['jpg_options']) - img.resize(settings['img_size']) - - if settings['copyright']: - img.add_copyright(settings['copyright']) - - img.save(outname, options=settings['jpg_options']) + generate_image(filepath, outname, settings['img_size'], None, + options=options, copyright_text=settings['copyright']) if settings['make_thumbs']: thumb_name = join(outpath, get_thumb(settings, filename)) - img.thumbnail(thumb_name, settings['thumb_size'], - fit=settings['thumb_fit'], - quality=settings['jpg_options']['quality']) + generate_thumbnail(outname, thumb_name, settings['thumb_size'], None, + fit=settings['thumb_fit'], options=options) def get_metadata(path): @@ -266,8 +267,8 @@ def get_metadata(path): - title - thumbnail image - description - """ + """ descfile = join(path, DESCRIPTION_FILE) if not os.path.isfile(descfile): @@ -279,11 +280,10 @@ def get_metadata(path): 'thumbnail': '' } else: - md = markdown.Markdown(extensions=['meta']) - with codecs.open(descfile, "r", "utf-8") as f: text = f.read() + md = markdown.Markdown(extensions=['meta']) html = md.convert(text) meta = { diff --git a/sigal/image.py b/sigal/image.py index 486bb68..3e9a67d 100644 --- a/sigal/image.py +++ b/sigal/image.py @@ -20,96 +20,60 @@ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. -from __future__ import division - import logging -import os from PIL import Image as PILImage from PIL import ImageDraw, ImageOps +from pilkit.processors import ProcessorPipeline, Transpose, ResizeToFill from pilkit.utils import save_image -# EXIF specs Orientation constant -EXIF_ORIENTATION_TAG = 274 +def generate_image(source, outname, size, format, options=None, + autoconvert=True, copyright_text=''): + """Image processor, rotate and resize the image. -class Image(object): - """Image container - - Prepare images: resize images, and create thumbnails with some options - (squared thumbs, ...). - - :param filename: path to an image + :param source: path to an image + :param options: dict with PIL options (quality, optimize, progressive) """ - def __init__(self, filename): - self.filename = filename - self.imgname = os.path.split(filename)[1] - self.logger = logging.getLogger(__name__) - self.img = PILImage.open(filename) + logger = logging.getLogger(__name__) + img = PILImage.open(source) + original_format = img.format - # Try to read exif metadata. This can fail if: - # - the image does not have exif (png files) -> AttributeError - # - PIL fail to read exif -> IOError - try: - exif = self.img._getexif() - except (IOError, AttributeError): - exif = False + # Run the processors + processors = [ + Transpose(), # use exif to rotate the img + ResizeToFill(*size) + ] + img = ProcessorPipeline(processors).process(img) - if exif: - # http://www.impulseadventure.com/photo/exif-orientation.html - orientation = exif.get(EXIF_ORIENTATION_TAG) - rotate_map = {3: 180, 6: -90, 8: 90} - rotation = rotate_map.get(orientation) - if rotation: - self.img = self.img.rotate(rotation) + if copyright_text: + add_copyright(img, copyright_text) - def save(self, filename, format='JPEG', options=None, autoconvert=True): - """Save the image. + format = format or img.format or original_format or 'JPEG' + logger.debug('Save resized image to {0} ({1})'.format(outname, format)) + save_image(img, outname, format, options=options, autoconvert=autoconvert) - Pass a dict with PIL options (quality, optimize, progressive). - """ - save_image(self.img, filename, format, options=options, - autoconvert=autoconvert) +def generate_thumbnail(source, outname, box, format, fit=True, options=None): + "Create a thumbnail image" - def resize(self, size): - """Resize the image. + logger = logging.getLogger(__name__) + img = PILImage.open(source) + original_format = img.format - - check if the image format is portrait or landscape and adjust `size`. - - compute the width and height ratio, and keep the min to resize the - image inside the `size` box without distorting it. + if fit: + img = ImageOps.fit(img, box, PILImage.ANTIALIAS) + else: + img.thumbnail(box, PILImage.ANTIALIAS) - :param size: tuple with the (with, height) to resize + format = format or img.format or original_format or 'JPEG' + logger.debug('Save thumnail image to {0} ({1})'.format(outname, format)) + save_image(img, outname, format, options=options, autoconvert=True) - """ - if self.img.size[0] > self.img.size[1]: - newsize = size - else: - newsize = (size[1], size[0]) +def add_copyright(img, text): + "Add a copyright to the image" - wratio = newsize[0] / self.img.size[0] - hratio = newsize[1] / self.img.size[1] - ratio = min(wratio, hratio) - newsize = (int(ratio * self.img.size[0]), - int(ratio * self.img.size[1])) - - if ratio < 1: - self.img = self.img.resize(newsize, PILImage.ANTIALIAS) - - def add_copyright(self, text): - "Add a copyright to the image" - - draw = ImageDraw.Draw(self.img) - draw.text((5, self.img.size[1] - 15), '\xa9 ' + text) - - def thumbnail(self, filename, box, fit=True, quality=90): - "Create a thumbnail image" - - if fit: - self.img = ImageOps.fit(self.img, box, PILImage.ANTIALIAS) - else: - self.img.thumbnail(box, PILImage.ANTIALIAS) - - self.img.save(filename, quality=quality) + draw = ImageDraw.Draw(img) + draw.text((5, img.size[1] - 15), '\xa9 ' + text) diff --git a/sigal/writer.py b/sigal/writer.py index 582747d..54d5c2c 100644 --- a/sigal/writer.py +++ b/sigal/writer.py @@ -33,7 +33,7 @@ from distutils.dir_util import copy_tree from jinja2 import Environment, FileSystemLoader, ChoiceLoader, PrefixLoader from jinja2.exceptions import TemplateNotFound -from .image import Image +from .image import generate_thumbnail from .settings import get_thumb from .pkgmeta import __url__ as sigal_link @@ -147,10 +147,11 @@ class Writer(object): # generate the thumbnail if it is missing (if # settings['make_thumbs'] is False) if not os.path.exists(thumb_path): - img = Image(os.path.join(self.output_dir, dpath, alb_thumb)) - img.thumbnail(thumb_path, self.settings['thumb_size'], - fit=self.settings['thumb_fit'], - quality=self.settings['jpg_quality']) + source = os.path.join(self.output_dir, dpath, alb_thumb) + generate_thumbnail( + source, thumb_path, self.settings['thumb_size'], + fit=self.settings['thumb_fit'], + quality=self.settings['jpg_options']['quality']) ctx['albums'].append({ 'url': d + '/' + self.url_ext, diff --git a/tests/test_image.py b/tests/test_image.py index 17194cd..9529bb6 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -1,35 +1,18 @@ # -*- coding:utf-8 -*- import os -from tempfile import mkdtemp -from shutil import rmtree -try: - import unittest2 as unittest -except ImportError: - import unittest # NOQA - -from sigal.image import Image +from sigal.image import generate_image, generate_thumbnail CURRENT_DIR = os.path.dirname(__file__) TEST_IMAGE = 'exo20101028-b-full.jpg' -class TestImage(unittest.TestCase): +def test_image(tmpdir): "Test the Image class." - def setUp(self): - self.temp_path = mkdtemp() - self.srcfile = os.path.join(CURRENT_DIR, 'sample', 'dir2', TEST_IMAGE) - self.dstfile = os.path.join(self.temp_path, TEST_IMAGE) - self.img = Image(self.srcfile) + srcfile = os.path.join(CURRENT_DIR, 'sample', 'dir2', TEST_IMAGE) + dstfile = str(tmpdir.join(TEST_IMAGE)) - def tearDown(self): - rmtree(self.temp_path) - - def test_imgname(self): - self.assertEqual(self.img.imgname, TEST_IMAGE) - - def test_save(self): - self.img.save(self.dstfile) - self.assertTrue(os.path.isfile(self.dstfile)) + generate_thumbnail(srcfile, dstfile, (200, 150)) + assert os.path.isfile(dstfile)