diff options
| author | 2016-11-15 14:18:46 -0300 | |
|---|---|---|
| committer | 2016-11-15 14:18:46 -0300 | |
| commit | ffb671c61a24a9086343b54bad080e145ff33fc5 (patch) | |
| tree | 2c5291f7a34edf4afdc8e07887a148291bfa3fa1 /nikola/image_processing.py | |
| parent | 4e3224c012df9f74f010eb92203520515e8537b9 (diff) | |
New upstream version 7.8.1upstream/7.8.1
Diffstat (limited to 'nikola/image_processing.py')
| -rw-r--r-- | nikola/image_processing.py | 116 |
1 files changed, 85 insertions, 31 deletions
diff --git a/nikola/image_processing.py b/nikola/image_processing.py index b6f8215..e0096b2 100644 --- a/nikola/image_processing.py +++ b/nikola/image_processing.py @@ -33,32 +33,72 @@ import lxml import re import gzip +import piexif + from nikola import utils Image = None try: - from PIL import Image, ExifTags # NOQA + from PIL import ExifTags, Image # NOQA except ImportError: try: - import Image as _Image import ExifTags + import Image as _Image Image = _Image except ImportError: pass +EXIF_TAG_NAMES = {} + class ImageProcessor(object): """Apply image operations.""" - image_ext_list_builtin = ['.jpg', '.png', '.jpeg', '.gif', '.svg', '.bmp', '.tiff'] + image_ext_list_builtin = ['.jpg', '.png', '.jpeg', '.gif', '.svg', '.svgz', '.bmp', '.tiff'] + + def _fill_exif_tag_names(self): + """Connect EXIF tag names to numeric values.""" + if not EXIF_TAG_NAMES: + for ifd in piexif.TAGS: + for tag, data in piexif.TAGS[ifd].items(): + EXIF_TAG_NAMES[tag] = data['name'] + + def filter_exif(self, exif, whitelist): + """Filter EXIF data as described in the documentation.""" + # Scenario 1: keep everything + if whitelist == {'*': '*'}: + return exif - def resize_image(self, src, dst, max_size, bigger_panoramas=True): + # Scenario 2: keep nothing + if whitelist == {}: + return None + + # Scenario 3: keep some + self._fill_exif_tag_names() + exif = exif.copy() # Don't modify in-place, it's rude + for k in list(exif.keys()): + if type(exif[k]) != dict: + pass # At least thumbnails have no fields + elif k not in whitelist: + exif.pop(k) # Not whitelisted, remove + elif k in whitelist and whitelist[k] == '*': + # Fully whitelisted, keep all + pass + else: + # Partially whitelisted + for tag in list(exif[k].keys()): + if EXIF_TAG_NAMES[tag] not in whitelist[k]: + exif[k].pop(tag) + + return exif or None + + def resize_image(self, src, dst, max_size, bigger_panoramas=True, preserve_exif_data=False, exif_whitelist={}): """Make a copy of the image in the requested size.""" if not Image or os.path.splitext(src)[1] in ['.svg', '.svgz']: self.resize_svg(src, dst, max_size, bigger_panoramas) return im = Image.open(src) - w, h = im.size + size = w, h = im.size if w > max_size or h > max_size: size = max_size, max_size @@ -66,30 +106,44 @@ class ImageProcessor(object): if bigger_panoramas and w > 2 * h: size = min(w, max_size * 4), min(w, max_size * 4) - try: - exif = im._getexif() - except Exception: - exif = None - if exif is not None: - for tag, value in list(exif.items()): - decoded = ExifTags.TAGS.get(tag, tag) + try: + exif = piexif.load(im.info["exif"]) + except KeyError: + exif = None + # Inside this if, we can manipulate exif as much as + # we want/need and it will be preserved if required + if exif is not None: + # Rotate according to EXIF + value = exif['0th'].get(piexif.ImageIFD.Orientation, 1) + if value in (3, 4): + im = im.transpose(Image.ROTATE_180) + elif value in (5, 6): + im = im.transpose(Image.ROTATE_270) + elif value in (7, 8): + im = im.transpose(Image.ROTATE_90) + if value in (2, 4, 5, 7): + im = im.transpose(Image.FLIP_LEFT_RIGHT) + exif['0th'][piexif.ImageIFD.Orientation] = 1 - if decoded == 'Orientation': - if value == 3: - im = im.rotate(180) - elif value == 6: - im = im.rotate(270) - elif value == 8: - im = im.rotate(90) - break - try: - im.thumbnail(size, Image.ANTIALIAS) + try: + im.thumbnail(size, Image.ANTIALIAS) + if exif is not None and preserve_exif_data: + # Put right size in EXIF data + w, h = im.size + if '0th' in exif: + exif["0th"][piexif.ImageIFD.ImageWidth] = w + exif["0th"][piexif.ImageIFD.ImageLength] = h + if 'Exif' in exif: + exif["Exif"][piexif.ExifIFD.PixelXDimension] = w + exif["Exif"][piexif.ExifIFD.PixelYDimension] = h + # Filter EXIF data as required + exif = self.filter_exif(exif, exif_whitelist) + im.save(dst, exif=piexif.dump(exif)) + else: im.save(dst) - except Exception as e: - self.logger.warn("Can't thumbnail {0}, using original " - "image as thumbnail ({1})".format(src, e)) - utils.copy_file(src, dst) - else: # Image is small + except Exception as e: + self.logger.warn("Can't process {0}, using original " + "image! ({1})".format(src, e)) utils.copy_file(src, dst) def resize_svg(self, src, dst, max_size, bigger_panoramas): @@ -98,10 +152,10 @@ class ImageProcessor(object): # Resize svg based on viewport hacking. # note that this can also lead to enlarged svgs if src.endswith('.svgz'): - with gzip.GzipFile(src) as op: + with gzip.GzipFile(src, 'rb') as op: xml = op.read() else: - with open(src) as op: + with open(src, 'rb') as op: xml = op.read() tree = lxml.etree.XML(xml) width = tree.attrib['width'] @@ -125,9 +179,9 @@ class ImageProcessor(object): tree.attrib.pop("height") tree.attrib['viewport'] = "0 0 %ipx %ipx" % (w, h) if dst.endswith('.svgz'): - op = gzip.GzipFile(dst, 'w') + op = gzip.GzipFile(dst, 'wb') else: - op = open(dst, 'w') + op = open(dst, 'wb') op.write(lxml.etree.tostring(tree)) op.close() except (KeyError, AttributeError) as e: |
