1#!/usr/local/bin/python3.8
2
3# Copyright (C) 2007-2010 www.stani.be
4#
5# This program is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program.  If not, see http://www.gnu.org/licenses/
17
18import os
19from io import StringIO
20from itertools import cycle
21from PIL import Image
22from PIL import ImageDraw
23from PIL import ImageEnhance
24from PIL import ImageOps, ImageChops, ImageFilter
25
26ALL_PALETTE_INDICES = set(range(256))
27CHECKBOARD = {}
28COLOR_MAP = [255] * 128 + [0] * 128
29WWW_CACHE = {}
30
31EXT_BY_FORMATS = {
32    'JPEG': ['JPG', 'JPEG', 'JPE'],
33    'TIFF': ['TIF', 'TIFF'],
34    'SVG': ['SVG', 'SVGZ'],
35}
36FORMATS_BY_EXT = {}
37for format, exts in EXT_BY_FORMATS.items():
38    for ext in exts:
39        FORMATS_BY_EXT[ext] = format
40
41CROSS = 'Cross'
42ROUNDED = 'Rounded'
43SQUARE = 'Square'
44
45CORNERS = [ROUNDED, SQUARE, CROSS]
46CORNER_ID = 'rounded_corner_r%d_f%d'
47CROSS_POS = (CROSS, CROSS, CROSS, CROSS)
48ROUNDED_POS = (ROUNDED, ROUNDED, ROUNDED, ROUNDED)
49ROUNDED_RECTANGLE_ID = 'rounded_rectangle_r%d_f%d_s%s_p%s'
50
51class InvalidWriteFormatError(Exception):
52    pass
53
54
55def drop_shadow(image, horizontal_offset=5, vertical_offset=5,
56                background_color=(255, 255, 255, 0), shadow_color=0x444444,
57                border=8, shadow_blur=3, force_background_color=False, cache=None):
58    """Add a gaussian blur drop shadow to an image.
59
60    :param image: The image to overlay on top of the shadow.
61    :param type: PIL Image
62    :param offset:
63
64        Offset of the shadow from the image as an (x,y) tuple.
65        Can be positive or negative.
66
67    :type offset: tuple of integers
68    :param background_color: Background color behind the image.
69    :param shadow_color: Shadow color (darkness).
70    :param border:
71
72        Width of the border around the image.  This must be wide
73        enough to account for the blurring of the shadow.
74
75    :param shadow_blur:
76
77        Number of times to apply the filter.  More shadow_blur
78        produce a more blurred shadow, but increase processing time.
79    """
80    if cache is None:
81        cache = {}
82
83    if has_transparency(image) and image.mode != 'RGBA':
84        # Make sure 'LA' and 'P' with trasparency are handled
85        image = image.convert('RGBA')
86
87    #get info
88    size = image.size
89    mode = image.mode
90
91    back = None
92
93    #assert image is RGBA
94    if mode != 'RGBA':
95        if mode != 'RGB':
96            image = image.convert('RGB')
97            mode = 'RGB'
98        #create cache id
99        id = ''.join([str(x) for x in ['shadow_', size,
100                                       horizontal_offset, vertical_offset, border, shadow_blur,
101                                       background_color, shadow_color]])
102
103        #look up in cache
104        if id in cache:
105            #retrieve from cache
106            back, back_size = cache[id]
107
108    if back is None:
109        #size of backdrop
110        back_size = (size[0] + abs(horizontal_offset) + 2 * border,
111                     size[1] + abs(vertical_offset) + 2 * border)
112
113        #create shadow mask
114        if mode == 'RGBA':
115            image_mask = get_alpha(image)
116            shadow = Image.new('L', back_size, 0)
117        else:
118            image_mask = Image.new(mode, size, shadow_color)
119            shadow = Image.new(mode, back_size, background_color)
120
121        shadow_left = border + max(horizontal_offset, 0)
122        shadow_top = border + max(vertical_offset, 0)
123        paste(shadow, image_mask, (shadow_left, shadow_top,
124                                   shadow_left + size[0], shadow_top + size[1]))
125        del image_mask  # free up memory
126
127        #blur shadow mask
128
129        #Apply the filter to blur the edges of the shadow.  Since a small
130        #kernel is used, the filter must be applied repeatedly to get a decent
131        #blur.
132        n = 0
133        while n < shadow_blur:
134            shadow = shadow.filter(ImageFilter.BLUR)
135            n += 1
136
137        #create back
138        if mode == 'RGBA':
139            back = Image.new('RGBA', back_size, shadow_color)
140            back.putalpha(shadow)
141            del shadow  # free up memory
142        else:
143            back = shadow
144            cache[id] = back, back_size
145
146    #Paste the input image onto the shadow backdrop
147    image_left = border - min(horizontal_offset, 0)
148    image_top = border - min(vertical_offset, 0)
149    if mode == 'RGBA':
150        paste(back, image, (image_left, image_top), image)
151        if force_background_color:
152            mask = get_alpha(back)
153            paste(back, Image.new('RGB', back.size, background_color),
154                  (0, 0), ImageChops.invert(mask))
155            back.putalpha(mask)
156    else:
157        paste(back, image, (image_left, image_top))
158
159    return back
160
161def round_image(image, cache={}, round_all=True, rounding_type=None,
162                radius=100, opacity=255, pos=ROUNDED_POS, back_color='#FFFFFF'):
163
164    if image.mode != 'RGBA':
165        image = image.convert('RGBA')
166
167    if round_all:
168        pos = 4 * (rounding_type, )
169
170    mask = create_rounded_rectangle(image.size, cache, radius, opacity, pos)
171
172    paste(image, Image.new('RGB', image.size, back_color), (0, 0),
173          ImageChops.invert(mask))
174    image.putalpha(mask)
175    return image
176
177def create_rounded_rectangle(size=(600, 400), cache={}, radius=100,
178                             opacity=255, pos=ROUNDED_POS):
179    #rounded_rectangle
180    im_x, im_y = size
181    rounded_rectangle_id = ROUNDED_RECTANGLE_ID % (radius, opacity, size, pos)
182    if rounded_rectangle_id in cache:
183        return cache[rounded_rectangle_id]
184    else:
185        #cross
186        cross_id = ROUNDED_RECTANGLE_ID % (radius, opacity, size, CROSS_POS)
187        if cross_id in cache:
188            cross = cache[cross_id]
189        else:
190            cross = cache[cross_id] = Image.new('L', size, 0)
191            draw = ImageDraw.Draw(cross)
192            draw.rectangle((radius, 0, im_x - radius, im_y), fill=opacity)
193            draw.rectangle((0, radius, im_x, im_y - radius), fill=opacity)
194        if pos == CROSS_POS:
195            return cross
196        #corner
197        corner_id = CORNER_ID % (radius, opacity)
198        if corner_id in cache:
199            corner = cache[corner_id]
200        else:
201            corner = cache[corner_id] = create_corner(radius, opacity)
202        #rounded rectangle
203        rectangle = Image.new('L', (radius, radius), 255)
204        rounded_rectangle = cross.copy()
205        for index, angle in enumerate(pos):
206            if angle == CROSS:
207                continue
208            if angle == ROUNDED:
209                element = corner
210            else:
211                element = rectangle
212            if index % 2:
213                x = im_x - radius
214                element = element.transpose(Image.FLIP_LEFT_RIGHT)
215            else:
216                x = 0
217            if index < 2:
218                y = 0
219            else:
220                y = im_y - radius
221                element = element.transpose(Image.FLIP_TOP_BOTTOM)
222            paste(rounded_rectangle, element, (x, y))
223        cache[rounded_rectangle_id] = rounded_rectangle
224        return rounded_rectangle
225
226def create_corner(radius=100, opacity=255, factor=2):
227    corner = Image.new('L', (factor * radius, factor * radius), 0)
228    draw = ImageDraw.Draw(corner)
229    draw.pieslice((0, 0, 2 * factor * radius, 2 * factor * radius),
230                  180, 270, fill=opacity)
231    corner = corner.resize((radius, radius), Image.ANTIALIAS)
232    return corner
233
234def get_format(ext):
235    """Guess the image format by the file extension.
236
237    :param ext: file extension
238    :type ext: string
239    :returns: image format
240    :rtype: string
241
242    .. warning::
243
244        This is only meant to check before saving files. For existing files
245        open the image with PIL and check its format attribute.
246
247    >>> get_format('jpg')
248    'JPEG'
249    """
250    ext = ext.lstrip('.').upper()
251    return FORMATS_BY_EXT.get(ext, ext)
252
253def open_image_data(data):
254    """Open image from format data.
255
256    :param data: image format data
257    :type data: string
258    :returns: image
259    :rtype: pil.Image
260    """
261    return Image.open(StringIO(data))
262
263
264def open_image_exif(uri):
265    """Open local files or remote files over http and transpose the
266    image to its exif orientation.
267
268    :param uri: image location
269    :type uri: string
270    :returns: image
271    :rtype: pil.Image
272    """
273    return transpose_exif(open_image(uri))
274
275
276class _ByteCounter:
277    """Helper class to count how many bytes are written to a file.
278
279    .. see also:: :func:`get_size`
280
281    >>> bc = _ByteCounter()
282    >>> bc.write('12345')
283    >>> bc.bytes
284    5
285    """
286    def __init__(self):
287        self.bytes = 0
288
289    def write(self, data):
290        self.bytes += len(data)
291
292
293def get_size(im, format, **options):
294    """Gets the size in bytes if the image would be written to a file.
295
296    :param format: image file format (e.g. ``'JPEG'``)
297    :type format: string
298    :returns: the file size in bytes
299    :rtype: int
300    """
301    try:
302        out = _ByteCounter()
303        im.save(out, format, **options)
304        return out.bytes
305    except AttributeError:
306        # fall back on full in-memory compression
307        out = StringIO()
308        im.save(out, format, **options)
309        return len(out.getvalue())
310
311
312def get_quality(im, size, format, down=0, up=100, delta=1000, options=None):
313    """Figure out recursively the quality save parameter to obtain a
314    certain image size. This mostly used for ``JPEG`` images.
315
316    :param im: image
317    :type im: pil.Image
318    :param format: image file format (e.g. ``'JPEG'``)
319    :type format: string
320    :param down: minimum file size in bytes
321    :type down: int
322    :param up: maximum file size in bytes
323    :type up: int
324    :param delta: fault tolerance in bytes
325    :type delta: int
326    :param options: image save options
327    :type options: dict
328    :returns: save quality
329    :rtype: int
330
331    Example::
332
333        filename = '/home/stani/sync/Desktop/IMGA3345.JPG'
334        im = Image.open(filename)
335        q = get_quality(im, 300000, "JPEG")
336        im.save(filename.replace('.jpg', '_sized.jpg'))
337    """
338    if options is None:
339        options = {}
340    q = options['quality'] = (down + up) / 2
341    if q == down or q == up:
342        return max(q, 1)
343    s = get_size(im, format, **options)
344    if abs(s - size) < delta:
345        return q
346    elif s > size:
347        return get_quality(im, size, format, down, up=q, options=options)
348    else:
349        return get_quality(im, size, format, down=q, up=up, options=options)
350
351
352def fill_background_color(image, color):
353    """Fills given image with background color.
354
355    :param image: source image
356    :type image: pil.Image
357    :param color: background color
358    :type color: tuple of int
359    :returns: filled image
360    :rtype: pil.Image
361    """
362    if image.mode == 'LA':
363        image = image.convert('RGBA')
364    elif image.mode != 'RGBA' and\
365            not (image.mode == 'P' and 'transparency' in image.info):
366        return image
367    if len(color) == 4 and color[-1] != 255:
368        mode = 'RGBA'
369    else:
370        mode = 'RGB'
371    back = Image.new(mode, image.size, color)
372    if (image.mode == 'P' and mode == 'RGBA'):
373        image = image.convert('RGBA')
374    if has_alpha(image):
375        paste(back, image, mask=image)
376    elif image.mode == 'P':
377        palette = image.getpalette()
378        index = image.info['transparency']
379        palette[index * 3: index * 3 + 3] = color[:3]
380        image.putpalette(palette)
381        del image.info['transparency']
382        back = image
383    else:
384        paste(back, image)
385    return back
386
387
388def generate_layer(image_size, mark, method,
389                   horizontal_offset, vertical_offset,
390                   horizontal_justification, vertical_justification,
391                   orientation, opacity):
392    """Generate new layer for backgrounds or watermarks on which a given
393    image ``mark`` can be positioned, scaled or repeated.
394
395    :param image_size: size of the reference image
396    :type image_size: tuple of int
397    :param mark: image mark
398    :type mark: pil.Image
399    :param method: ``'Tile'``, ``'Scale'``, ``'By Offset'``
400    :type method: string
401    :param horizontal_offset: horizontal offset
402    :type horizontal_offset: int
403    :param vertical_offset: vertical offset
404    :type vertical_offset: int
405    :param horizontal_justification: ``'Left'``, ``'Middle'``, ``'Right'``
406    :type horizontal_justification: string
407    :param vertical_justification: ``'Top'``, ``'Middle'``, ``'Bottom'``
408    :type vertical_justification: string
409    :param orientation: mark orientation (e.g. ``'ROTATE_270'``)
410    :type orientation: string
411    :param opacity: opacity within ``[0, 1]``
412    :type opacity: float
413    :returns: generated layer
414    :rtype: pil.Image
415
416    .. see also:: :func:`reduce_opacity`
417    """
418    mark = convert_safe_mode(open_image(mark))
419    opacity /= 100.0
420    mark = reduce_opacity(mark, opacity)
421    layer = Image.new('RGBA', image_size, (0, 0, 0, 0))
422    if method == 'Tile':
423        for y in range(0, image_size[1], mark.size[1]):
424            for x in range(0, image_size[0], mark.size[0]):
425                paste(layer, mark, (x, y))
426    elif method == 'Scale':
427        # scale, but preserve the aspect ratio
428        ratio = min(float(image_size[0]) / mark.size[0],
429                    float(image_size[1]) / mark.size[1])
430        w = int(mark.size[0] * ratio)
431        h = int(mark.size[1] * ratio)
432        mark = mark.resize((w, h))
433        paste(layer, mark, ((image_size[0] - w) / 2,
434                            (image_size[1] - h) / 2))
435    elif method == 'By Offset':
436        location = calculate_location(
437            horizontal_offset, vertical_offset,
438            horizontal_justification, vertical_justification,
439            image_size, mark.size)
440        if orientation:
441            orientation_value = getattr(Image, orientation)
442            mark = mark.transpose(orientation_value)
443        paste(layer, mark, location, force=True)
444    else:
445        raise ValueError('Unknown method "%s" for generate_layer.' % method)
446    return layer
447
448
449def identity_color(image, value=0):
450    """Get a color with same color component values.
451
452    >>> im = Image.new('RGB', (1,1))
453    >>> identity_color(im, 2)
454    (2, 2, 2)
455    >>> im = Image.new('L', (1,1))
456    >>> identity_color(im, 7)
457    7
458    """
459    bands = image.getbands()
460    if len(bands) == 1:
461        return value
462    return tuple([value for band in bands])
463
464
465def blend(im1, im2, amount, color=None):
466    """Blend two images with each other. If the images differ in size
467    the color will be used for undefined pixels.
468
469    :param im1: first image
470    :type im1: pil.Image
471    :param im2: second image
472    :type im2: pil.Image
473    :param amount: amount of blending
474    :type amount: int
475    :param color: color of undefined pixels
476    :type color: tuple
477    :returns: blended image
478    :rtype: pil.Image
479    """
480    im2 = convert_safe_mode(im2)
481    if im1.size == im2.size:
482        im1 = convert(im1, im2.mode)
483    else:
484        if color is None:
485            expanded = Image.new(im2.mode, im2.size)
486        elif im2.mode in ('1', 'L') and type(color) != int:
487            expanded = Image.new(im2.mode, im2.size, color[0])
488        else:
489            expanded = Image.new(im2.mode, im2.size, color)
490        im1 = im1.convert(expanded.mode)
491        we, he = expanded.size
492        wi, hi = im1.size
493        paste(expanded, im1, ((we - wi) / 2, (he - hi) / 2),
494              im1.convert('RGBA'))
495        im1 = expanded
496    return Image.blend(im1, im2, amount)
497
498
499def reduce_opacity(im, opacity):
500    """Returns an image with reduced opacity if opacity is
501    within ``[0, 1]``.
502
503    :param im: source image
504    :type im: pil.Image
505    :param opacity: opacity within ``[0, 1]``
506    :type opacity: float
507    :returns im: image
508    :rtype: pil.Image
509
510    >>> im = Image.new('RGBA', (1, 1), (255, 255, 255))
511    >>> im = reduce_opacity(im, 0.5)
512    >>> im.getpixel((0,0))
513    (255, 255, 255, 127)
514    """
515    if opacity < 0 or opacity > 1:
516        return im
517    alpha = get_alpha(im)
518    alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
519    put_alpha(im, alpha)
520    return im
521
522
523def calculate_location(horizontal_offset, vertical_offset,
524                       horizontal_justification, vertical_justification,
525                       canvas_size, image_size):
526    """Calculate location based on offset and justification. Offsets
527    can be positive and negative.
528
529    :param horizontal_offset: horizontal offset
530    :type horizontal_offset: int
531    :param vertical_offset: vertical offset
532    :type vertical_offset: int
533    :param horizontal_justification: ``'Left'``, ``'Middle'``, ``'Right'``
534    :type horizontal_justification: string
535    :param vertical_justification: ``'Top'``, ``'Middle'``, ``'Bottom'``
536    :type vertical_justification: string
537    :param canvas_size: size of the total canvas
538    :type canvas_size: tuple of int
539    :param image_size: size of the image/text which needs to be placed
540    :type image_size: tuple of int
541    :returns: location
542    :rtype: tuple of int
543
544    .. see also:: :func:`generate layer`
545
546    >>> calculate_location(50, 50, 'Left', 'Middle', (100,100), (10,10))
547    (50, 45)
548    """
549    canvas_width, canvas_height = canvas_size
550    image_width, image_height = image_size
551
552    # check offsets
553    if horizontal_offset < 0:
554        horizontal_offset += canvas_width
555    if vertical_offset < 0:
556        vertical_offset += canvas_height
557
558    # check justifications
559    if horizontal_justification == 'Left':
560        horizontal_delta = 0
561    elif horizontal_justification == 'Middle':
562        horizontal_delta = -image_width / 2
563    elif horizontal_justification == 'Right':
564        horizontal_delta = -image_width
565
566    if vertical_justification == 'Top':
567        vertical_delta = 0
568    elif vertical_justification == 'Middle':
569        vertical_delta = -image_height / 2
570    elif vertical_justification == 'Bottom':
571        vertical_delta = -image_height
572
573    return horizontal_offset + horizontal_delta, \
574        vertical_offset + vertical_delta
575
576
577####################################
578####    PIL helper functions    ####
579####################################
580
581
582def flatten(l):
583    """Flatten a list.
584
585    :param l: list to be flattened
586    :type l: list
587    :returns: flattened list
588    :rtype: list
589
590    >>> flatten([[1, 2], [3]])
591    [1, 2, 3]
592    """
593    return [item for sublist in l for item in sublist]
594
595
596def has_alpha(image):
597    """Checks if the image has an alpha band.
598    i.e. the image mode is either RGBA or LA.
599    The transparency in the P mode doesn't count as an alpha band
600
601    :param image: the image to check
602    :type image: PIL image object
603    :returns: True or False
604    :rtype: boolean
605    """
606    return image.mode.endswith('A')
607
608
609def has_transparency(image):
610    """Checks if the image has transparency.
611    The image has an alpha band or a P mode with transparency.
612
613    :param image: the image to check
614    :type image: PIL image object
615    :returns: True or False
616    :rtype: boolean
617    """
618    return (image.mode == 'P' and 'transparency' in image.info) or\
619        has_alpha(image)
620
621
622def get_alpha(image):
623    """Gets the image alpha band. Can handles P mode images with transpareny.
624    Returns a band with all values set to 255 if no alpha band exists.
625
626    :param image: input image
627    :type image: PIL image object
628    :returns: alpha as a band
629    :rtype: single band image object
630    """
631    if has_alpha(image):
632        return image.split()[-1]
633    if image.mode == 'P' and 'transparency' in image.info:
634        return image.convert('RGBA').split()[-1]
635    # No alpha layer, create one.
636    return Image.new('L', image.size, 255)
637
638
639def get_format_data(image, format):
640    """Convert the image in the file bytes of the image. By consequence
641    this byte data is different for the chosen format (``JPEG``,
642    ``TIFF``, ...).
643
644    .. see also:: :func:`thumbnail.get_format_data`
645
646    :param image: source image
647    :type impage: pil.Image
648    :param format: image file type format
649    :type format: string
650    :returns: byte data of the image
651    """
652    f = StringIO()
653    convert_save_mode_by_format(image, format).save(f, format)
654    return f.getvalue()
655
656
657def get_palette(image):
658    """Gets the palette of an image as a sequence of (r, g, b) tuples.
659
660    :param image: image with a palette
661    :type impage: pil.Image
662    :returns: palette colors
663    :rtype: a sequence of (r, g, b) tuples
664    """
665    palette = image.resize((256, 1))
666    palette.putdata(range(256))
667    return list(palette.convert("RGB").getdata())
668
669
670def get_used_palette_indices(image):
671    """Get used color indices in an image palette.
672
673    :param image: image with a palette
674    :type impage: pil.Image
675    :returns: used colors of the palette
676    :rtype: set of integers (0-255)
677    """
678    return set(image.getdata())
679
680
681def get_used_palette_colors(image):
682    """Get used colors in an image palette as a sequence of (r, g, b) tuples.
683
684    :param image: image with a palette
685    :type impage: pil.Image
686    :returns: used colors of the palette
687    :rtype: sequence of (r, g, b) tuples
688    """
689    used_indices = get_used_palette_indices(image)
690    if 'transparency' in image.info:
691        used_indices -= set([image.info['transparency']])
692    n = len(used_indices)
693    palette = image.resize((n, 1))
694    palette.putdata(used_indices)
695    return palette.convert("RGB").getdata()
696
697
698def get_unused_palette_indices(image):
699    """Get unused color indices in an image palette.
700
701    :param image: image with a palette
702    :type impage: pil.Image
703    :returns: unused color indices of the palette
704    :rtype: set of 0-255
705    """
706    return ALL_PALETTE_INDICES - get_used_palette_indices(image)
707
708
709def fit_color_in_palette(image, color):
710    """Fit a color into a palette. If the color exists already in the palette
711    return its current index, otherwise add the color to the palette if
712    possible. Returns -1 for color index if all colors are used already.
713
714    :param image: image with a palette
715    :type image: pil.Image
716    :param color: color to fit
717    :type color: (r, g, b) tuple
718    :returns: color index, (new) palette
719    :rtype: (r, g, b) tuple, sequence of (r, g, b) tuples
720    """
721    palette = get_palette(image)
722    try:
723        index = palette.index(color)
724    except ValueError:
725        index = -1
726    if index > -1:
727        # Check if it is not the transparent index, as that doesn't qualify.
728        try:
729            transparent = index == image.info['transparency']
730        except KeyError:
731            transparent = False
732        # If transparent, look further
733        if transparent:
734            try:
735                index = palette[index + 1:].index(color) + index + 1
736            except ValueError:
737                index = -1
738    if index == -1:
739        unused = list(get_unused_palette_indices(image))
740        if unused:
741            index = unused[0]
742            palette[index] = color  # add color to palette
743        else:
744            palette = None  # palette is full
745    return index, palette
746
747
748def put_palette(image_to, image_from, palette=None):
749    """Copies the palette and transparency of one image to another.
750
751    :param image_to: image with a palette
752    :type image_to: pil.Image
753    :param image_from: image with a palette
754    :type image_from: pil.Image
755    :param palette: image palette
756    :type palette: sequence of (r, g, b) tuples or None
757    """
758    if palette == None:
759        palette = get_palette(image_from)
760    image_to.putpalette(flatten(palette))
761    if 'transparency' in image_from.info:
762        image_to.info['transparency'] = image_from.info['transparency']
763
764
765def put_alpha(image, alpha):
766    """Copies the given band to the alpha layer of the given image.
767
768    :param image: input image
769    :type image: PIL image object
770    :param alpha: the alpha band to copy
771    :type alpha: single band image object
772    """
773    if image.mode in ['CMYK', 'YCbCr', 'P']:
774        image = image.convert('RGBA')
775    elif image.mode in ['1', 'F']:
776        image = image.convert('RGBA')
777    image.putalpha(alpha)
778
779
780def remove_alpha(image):
781    """Returns a copy of the image after removing the alpha band or
782    transparency
783
784    :param image: input image
785    :type image: PIL image object
786    :returns: the input image after removing the alpha band or transparency
787    :rtype: PIL image object
788    """
789    if image.mode == 'RGBA':
790        return image.convert('RGB')
791    if image.mode == 'LA':
792        return image.convert('L')
793    if image.mode == 'P' and 'transparency' in image.info:
794        img = image.convert('RGB')
795        del img.info['transparency']
796        return img
797    return image
798
799
800def paste(destination, source, box=(0, 0), mask=None, force=False):
801    """"Pastes the source image into the destination image while using an
802    alpha channel if available.
803
804    :param destination: destination image
805    :type destination:  PIL image object
806    :param source: source image
807    :type source: PIL image object
808    :param box:
809
810        The box argument is either a 2-tuple giving the upper left corner,
811        a 4-tuple defining the left, upper, right, and lower pixel coordinate,
812        or None (same as (0, 0)). If a 4-tuple is given, the size of the
813        pasted image must match the size of the region.
814
815    :type box: tuple
816    :param mask: mask or None
817
818    :type mask: bool or PIL image object
819    :param force:
820
821        With mask: Force the invert alpha paste or not.
822
823        Without mask:
824
825        - If ``True`` it will overwrite the alpha channel of the destination
826          with the alpha channel of the source image. So in that case the
827          pixels of the destination layer will be abandoned and replaced
828          by exactly the same pictures of the destination image. This is mostly
829          what you need if you paste on a transparant canvas.
830        - If ``False`` this will use a mask when the image has an alpha
831          channel. In this case pixels of the destination image will appear
832          through where the source image is transparent.
833
834    :type force: bool
835    """
836    # Paste on top
837    if mask and source == mask:
838        if has_alpha(source):
839            # invert_alpha = the transparant pixels of the destination
840            if has_alpha(destination) and (destination.size == source.size
841                                           or force):
842                invert_alpha = ImageOps.invert(get_alpha(destination))
843                if invert_alpha.size != source.size:
844                    # if sizes are not the same be careful!
845                    # check the results visually
846                    if len(box) == 2:
847                        w, h = source.size
848                        box = (box[0], box[1], box[0] + w, box[1] + h)
849                    invert_alpha = invert_alpha.crop(box)
850            else:
851                invert_alpha = None
852            # we don't want composite of the two alpha channels
853            source_without_alpha = remove_alpha(source)
854            # paste on top of the opaque destination pixels
855            destination.paste(source_without_alpha, box, source)
856            if invert_alpha != None:
857                # the alpha channel is ok now, so save it
858                destination_alpha = get_alpha(destination)
859                # paste on top of the transparant destination pixels
860                # the transparant pixels of the destination should
861                # be filled with the color information from the source
862                destination.paste(source_without_alpha, box, invert_alpha)
863                # restore the correct alpha channel
864                destination.putalpha(destination_alpha)
865        else:
866            destination.paste(source, box)
867    elif mask:
868        destination.paste(source, box, mask)
869    else:
870        destination.paste(source, box)
871        if force and has_alpha(source):
872            destination_alpha = get_alpha(destination)
873            source_alpha = get_alpha(source)
874            destination_alpha.paste(source_alpha, box)
875            destination.putalpha(destination_alpha)
876
877
878def auto_crop(image):
879    """Crops all transparent or black background from the image
880    :param image: input image
881    :type image: PIL image object
882    :returns: the cropped image
883    :rtype: PIL image object
884    """
885
886    alpha = get_alpha(image)
887    box = alpha.getbbox()
888    return convert_safe_mode(image).crop(box)
889
890
891def convert(image, mode, *args, **keyw):
892    """Returns a converted copy of an image
893
894    :param image: input image
895    :type image: PIL image object
896    :param mode: the new mode
897    :type mode: string
898    :param args: extra options
899    :type args: tuple of values
900    :param keyw: extra keyword options
901    :type keyw: dictionary of options
902    :returns: the converted image
903    :rtype: PIL image object
904    """
905    if mode == 'P':
906        if image.mode == 'P':
907            return image
908        if image.mode in ['1', 'F']:
909            return image.convert('L').convert(mode, *args, **keyw)
910        if image.mode in ['RGBA', 'LA']:
911            alpha = get_alpha(image)
912            output = image.convert('RGB').convert(
913                mode, colors=255, *args, **keyw)
914            paste(output,
915                  255, alpha.point(COLOR_MAP))
916            output.info['transparency'] = 255
917            return output
918        return image.convert('RGB').convert(mode, *args, **keyw)
919    if image.mode == 'P' and mode == 'LA':
920        # A workaround for a PIL bug.
921        # Converting from P to LA directly doesn't work.
922        return image.convert('RGBA').convert('LA', *args, **keyw)
923    if has_transparency(image) and (not mode in ['RGBA', 'LA']):
924        if image.mode == 'P':
925            image = image.convert('RGBA')
926            del image.info['transparency']
927        #image = fill_background_color(image, (255, 255, 255, 255))
928        image = image.convert(mode, *args, **keyw)
929        return image
930    return image.convert(mode, *args, **keyw)
931
932
933def convert_safe_mode(image):
934    """Converts image into a processing-safe mode.
935
936    :param image: input image
937    :type image: PIL image object
938    :returns: the converted image
939    :rtype: PIL image object
940    """
941    if image.mode in ['1', 'F']:
942        return image.convert('L')
943    if image.mode == 'P' and 'transparency' in image.info:
944        img = image.convert('RGBA')
945        del img.info['transparency']
946        return img
947    if image.mode in ['P', 'YCbCr', 'CMYK', 'RGBX']:
948        return image.convert('RGB')
949    return image
950
951
952def convert_save_mode_by_format(image, format):
953    """Converts image into a saving-safe mode.
954
955    :param image: input image
956    :type image: PIL image object
957    :param format: target format
958    :type format: string
959    :returns: the converted image
960    :rtype: PIL image object
961    """
962    #TODO: Extend this helper function to support other formats as well
963    if image.mode == 'P':
964        # Make sure P is handled correctly
965        if not format in ['GIF', 'PNG', 'TIFF', 'IM', 'PCX']:
966            image = remove_alpha(image)
967    if format == 'JPEG':
968        if image.mode in ['RGBA', 'P']:
969            return image.convert('RGB')
970        if image.mode in ['LA']:
971            return image.convert('L')
972    elif format == 'BMP':
973        if image.mode in ['LA']:
974            return image.convert('L')
975        if image.mode in ['P', 'RGBA', 'YCbCr', 'CMYK']:
976            return image.convert('RGB')
977    elif format == 'DIB':
978        if image.mode in ['YCbCr', 'CMYK']:
979            return image.convert('RGB')
980    elif format == 'EPS':
981        if image.mode in ['1', 'LA']:
982            return image.convert('L')
983        if image.mode in ['P', 'RGBA', 'YCbCr']:
984            return image.convert('RGB')
985    elif format == 'GIF':
986        return convert(image, 'P', palette=Image.ADAPTIVE)
987    elif format == 'PBM':
988        if image.mode != '1':
989            return image.convert('1')
990    elif format == 'PCX':
991        if image.mode in ['RGBA', 'CMYK', 'YCbCr']:
992            return image.convert('RGB')
993        if image.mode in ['LA', '1']:
994            return image.convert('L')
995    elif format == 'PDF':
996        if image.mode in ['LA']:
997            return image.convert('L')
998        if image.mode in ['RGBA', 'YCbCr']:
999            return image.convert('RGB')
1000    elif format == 'PGM':
1001        if image.mode != 'L':
1002            return image.convert('L')
1003    elif format == 'PPM':
1004        if image.mode in ['P', 'CMYK', 'YCbCr']:
1005            return image.convert('RGB')
1006        if image.mode in ['LA']:
1007            return image.convert('L')
1008    elif format == 'PS':
1009        if image.mode in ['1', 'LA']:
1010            return image.convert('L')
1011        if image.mode in ['P', 'RGBA', 'YCbCr']:
1012            return image.convert('RGB')
1013    elif format == 'XBM':
1014        if not image.mode in ['1']:
1015            return image.convert('1')
1016    elif format == 'TIFF':
1017        if image.mode in ['YCbCr']:
1018            return image.convert('RGB')
1019    elif format == 'PNG':
1020        if image.mode in ['CMYK', 'YCbCr']:
1021            return image.convert('RGB')
1022    #for consistency return a copy! (thumbnail.py depends on it)
1023    return image.copy()
1024
1025
1026def save_check_mode(image, filename, **options):
1027    #save image with pil
1028    save(image, filename, **options)
1029    #verify saved file
1030    try:
1031        image_file = Image.open(filename)
1032        image_file.verify()
1033    except IOError:
1034        # We can't verify the image mode with PIL, so issue no warnings.
1035        return ''
1036    if image.mode != image_file.mode:
1037        return image_file.mode
1038    return ''
1039
1040
1041def save_safely(image, filename):
1042    """Saves an image with a filename and raise the specific
1043    ``InvalidWriteFormatError`` in case of an error instead of a
1044    ``KeyError``. It can also save IM files with unicode.
1045
1046    :param image: image
1047    :type image: pil.Image
1048    :param filename: image filename
1049    :type filename: string
1050    """
1051    ext = os.path.splitext(filename)[-1]
1052    format = get_format(ext[1:])
1053    image = convert_save_mode_by_format(image, format)
1054    save(image, filename)
1055
1056
1057def get_reverse_transposition(transposition):
1058    """Get the reverse transposition method.
1059
1060    :param transposition: transpostion, e.g. ``Image.ROTATE_90``
1061    :returns: inverse transpostion, e.g. ``Image.ROTATE_270``
1062    """
1063    if transposition == Image.ROTATE_90:
1064        return Image.ROTATE_270
1065    elif transposition == Image.ROTATE_270:
1066        return Image.ROTATE_90
1067    return transposition
1068
1069
1070def get_exif_transposition(orientation):
1071    """Get the transposition methods necessary to aling the image to
1072    its exif orientation.
1073
1074    :param orientation: exif orientation
1075    :type orientation: int
1076    :returns: (transposition methods, reverse transpostion methods)
1077    :rtype: tuple
1078    """
1079    #see EXIF.py
1080    if orientation == 1:
1081        transposition = transposition_reverse = ()
1082    elif orientation == 2:
1083        transposition = Image.FLIP_LEFT_RIGHT,
1084        transposition_reverse = Image.FLIP_LEFT_RIGHT,
1085    elif orientation == 3:
1086        transposition = Image.ROTATE_180,
1087        transposition_reverse = Image.ROTATE_180,
1088    elif orientation == 4:
1089        transposition = Image.FLIP_TOP_BOTTOM,
1090        transposition_reverse = Image.FLIP_TOP_BOTTOM,
1091    elif orientation == 5:
1092        transposition = Image.FLIP_LEFT_RIGHT, \
1093            Image.ROTATE_90
1094        transposition_reverse = Image.ROTATE_270, \
1095            Image.FLIP_LEFT_RIGHT
1096    elif orientation == 6:
1097        transposition = Image.ROTATE_270,
1098        transposition_reverse = Image.ROTATE_90,
1099    elif orientation == 7:
1100        transposition = Image.FLIP_LEFT_RIGHT, \
1101            Image.ROTATE_270
1102        transposition_reverse = Image.ROTATE_90, \
1103            Image.FLIP_LEFT_RIGHT
1104    elif orientation == 8:
1105        transposition = Image.ROTATE_90,
1106        transposition_reverse = Image.ROTATE_270,
1107    else:
1108        transposition = transposition_reverse = ()
1109    return transposition, transposition_reverse
1110
1111
1112def get_exif_orientation(image):
1113    """Gets the exif orientation of an image.
1114
1115    :param image: image
1116    :type image: pil.Image
1117    :returns: orientation
1118    :rtype: int
1119    """
1120    if not hasattr(image, '_getexif'):
1121        return 1
1122    try:
1123        _exif = image._getexif()
1124        if not _exif:
1125            return 1
1126        return _exif[0x0112]
1127    except KeyError:
1128        return 1
1129
1130
1131def transpose(image, methods):
1132    """Transpose with a sequence of transformations, mainly useful
1133    for exif.
1134
1135    :param image: image
1136    :type image: pil.Image
1137    :param methods: transposition methods
1138    :type methods: list
1139    :returns: transposed image
1140    :rtype: pil.Image
1141    """
1142    for method in methods:
1143        image = image.transpose(method)
1144    return image
1145
1146
1147def transpose_exif(image, reverse=False):
1148    """Transpose an image to its exif orientation.
1149
1150    :param image: image
1151    :type image: pil.Image
1152    :param reverse: False when opening, True when saving
1153    :type reverse: bool
1154    :returns: transposed image
1155    :rtype: pil.Image
1156    """
1157    orientation = get_exif_orientation(image)
1158    transposition = get_exif_transposition(orientation)[int(reverse)]
1159    if transposition:
1160        return transpose(image, transposition)
1161    return image
1162
1163
1164def checkboard(size, delta=8, fg=(128, 128, 128), bg=(204, 204, 204)):
1165    """Draw an n x n checkboard, which is often used as background
1166    for transparent images. The checkboards are stored in the
1167    ``CHECKBOARD`` cache.
1168
1169    :param delta: dimension of one square
1170    :type delta: int
1171    :param fg: foreground color
1172    :type fg: tuple of int
1173    :param bg: background color
1174    :type bg: tuple of int
1175    :returns: checkboard image
1176    :rtype: pil.Image
1177    """
1178    if not (size in CHECKBOARD):
1179        dim = max(size)
1180        n = int(dim / delta) + 1  # FIXME: now acts like square->nx, ny
1181
1182        def sq_start(i):
1183            "Return the x/y start coord of the square at column/row i."
1184            return i * delta
1185
1186        def square(i, j):
1187            "Return the square corners"
1188            return map(sq_start, [i, j, i + 1, j + 1])
1189
1190        image = Image.new("RGB", size, bg)
1191        draw_square = ImageDraw.Draw(image).rectangle
1192        squares = (square(i, j)
1193                   for i_start, j in zip(cycle((0, 1)), range(n))
1194                   for i in range(i_start, n, 2))
1195        for sq in squares:
1196            draw_square(sq, fill=fg)
1197        CHECKBOARD[size] = image
1198    return CHECKBOARD[size].copy()
1199
1200
1201def add_checkboard(image):
1202    """"If the image has a transparent mask, a RGB checkerboard will be
1203    drawn in the background.
1204
1205    .. note::
1206
1207        In case of a thumbnail, the resulting image can not be used for
1208        the cache, as it replaces the transparency layer with a non
1209        transparent checkboard.
1210
1211    :param image: image
1212    :type image: pil.Image
1213    :returns: image, with checkboard if transparant
1214    :rtype: pil.Image
1215    """
1216    if (image.mode == 'P' and 'transparency' in image.info) or\
1217            image.mode.endswith('A'):
1218        #transparant image
1219        image = image.convert('RGBA')
1220        image_bg = checkboard(image.size)
1221        paste(image_bg, image, (0, 0), image)
1222        return image_bg
1223    else:
1224        return image
1225