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