From 5cd454bde27eab02dbcfc0adf0657a018cf5db47 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 14 Apr 2014 12:30:32 +0300 Subject: [PATCH 01/33] Fix docstring printing from __main__, and pyflakes and some pep8 --- PIL/ImageCms.py | 154 +++++++++++++++++++++++++++--------------------- 1 file changed, 87 insertions(+), 67 deletions(-) diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py index c875712c1..5decaf704 100644 --- a/PIL/ImageCms.py +++ b/PIL/ImageCms.py @@ -1,19 +1,19 @@ -# -# The Python Imaging Library. -# $Id$ -# -# optional color managment support, based on Kevin Cazabon's PyCMS -# library. -# -# History: -# 2009-03-08 fl Added to PIL. -# -# Copyright (C) 2002-2003 Kevin Cazabon -# Copyright (c) 2009 by Fredrik Lundh -# -# See the README file for information on usage and redistribution. See -# below for the original description. -# +""" +The Python Imaging Library. +$Id$ + +Optional color managment support, based on Kevin Cazabon's PyCMS +library. + +History: +2009-03-08 fl Added to PIL. + +Copyright (C) 2002-2003 Kevin Cazabon +Copyright (c) 2009 by Fredrik Lundh + +See the README file for information on usage and redistribution. See +below for the original description. +""" from __future__ import print_function @@ -88,7 +88,7 @@ try: from PIL import _imagingcms except ImportError as ex: # Allow error import for doc purposes, but error out when accessing - # anything in core. + # anything in core. from _util import import_err _imagingcms = import_err(ex) from PIL._util import isStringType @@ -113,22 +113,22 @@ DIRECTION_PROOF = 2 FLAGS = { "MATRIXINPUT": 1, "MATRIXOUTPUT": 2, - "MATRIXONLY": (1|2), - "NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot - "NOPRELINEARIZATION": 16, # Don't create prelinearization tables on precalculated transforms (internal use) - "GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink) - "NOTCACHE": 64, # Inhibit 1-pixel cache + "MATRIXONLY": (1 | 2), + "NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot + "NOPRELINEARIZATION": 16, # Don't create prelinearization tables on precalculated transforms (internal use) + "GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink) + "NOTCACHE": 64, # Inhibit 1-pixel cache "NOTPRECALC": 256, - "NULLTRANSFORM": 512, # Don't transform anyway - "HIGHRESPRECALC": 1024, # Use more memory to give better accurancy - "LOWRESPRECALC": 2048, # Use less memory to minimize resouces + "NULLTRANSFORM": 512, # Don't transform anyway + "HIGHRESPRECALC": 1024, # Use more memory to give better accurancy + "LOWRESPRECALC": 2048, # Use less memory to minimize resouces "WHITEBLACKCOMPENSATION": 8192, "BLACKPOINTCOMPENSATION": 8192, - "GAMUTCHECK": 4096, # Out of Gamut alarm - "SOFTPROOFING": 16384, # Do softproofing - "PRESERVEBLACK": 32768, # Black preservation - "NODEFAULTRESOURCEDEF": 16777216, # CRD special - "GRIDPOINTS": lambda n: ((n) & 0xFF) << 16 # Gridpoints + "GAMUTCHECK": 4096, # Out of Gamut alarm + "SOFTPROOFING": 16384, # Do softproofing + "PRESERVEBLACK": 32768, # Black preservation + "NODEFAULTRESOURCEDEF": 16777216, # CRD special + "GRIDPOINTS": lambda n: ((n) & 0xFF) << 16 # Gridpoints } _MAX_FLAG = 0 @@ -136,6 +136,7 @@ for flag in FLAGS.values(): if isinstance(flag, int): _MAX_FLAG = _MAX_FLAG | flag + # --------------------------------------------------------------------. # Experimental PIL-level API # --------------------------------------------------------------------. @@ -153,18 +154,19 @@ class ImageCmsProfile: elif hasattr(profile, "read"): self._set(core.profile_frombytes(profile.read())) else: - self._set(profile) # assume it's already a profile + self._set(profile) # assume it's already a profile def _set(self, profile, filename=None): self.profile = profile self.filename = filename if profile: - self.product_name = None #profile.product_name - self.product_info = None #profile.product_info + self.product_name = None # profile.product_name + self.product_info = None # profile.product_info else: self.product_name = None self.product_info = None + class ImageCmsTransform(Image.ImagePointHandler): """Transform. This can be used with the procedural API, or with the standard Image.point() method. @@ -179,14 +181,14 @@ class ImageCmsTransform(Image.ImagePointHandler): input_mode, output_mode, intent, flags - ) + ) else: self.transform = core.buildProofTransform( input.profile, output.profile, proof.profile, input_mode, output_mode, intent, proof_intent, flags - ) + ) # Note: inputMode and outputMode are for pyCMS compatibility only self.input_mode = self.inputMode = input_mode self.output_mode = self.outputMode = output_mode @@ -198,21 +200,22 @@ class ImageCmsTransform(Image.ImagePointHandler): im.load() if imOut is None: imOut = Image.new(self.output_mode, im.size, None) - result = self.transform.apply(im.im.id, imOut.im.id) + self.transform.apply(im.im.id, imOut.im.id) return imOut def apply_in_place(self, im): im.load() if im.mode != self.output_mode: - raise ValueError("mode mismatch") # wrong output mode - result = self.transform.apply(im.im.id, im.im.id) + raise ValueError("mode mismatch") # wrong output mode + self.transform.apply(im.im.id, im.im.id) return im + def get_display_profile(handle=None): """ (experimental) Fetches the profile for the current display device. :returns: None if the profile is not known. """ - + import sys if sys.platform == "win32": from PIL import ImageWin @@ -229,6 +232,7 @@ def get_display_profile(handle=None): profile = get() return ImageCmsProfile(profile) + # --------------------------------------------------------------------. # pyCMS compatible layer # --------------------------------------------------------------------. @@ -237,6 +241,7 @@ class PyCMSError(Exception): """ (pyCMS) Exception class. This is used for all errors in the pyCMS API. """ pass + def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL, outputMode=None, inPlace=0, flags=0): """ (pyCMS) Applies an ICC transformation to a given image, mapping from @@ -288,7 +293,7 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER if outputMode is None: outputMode = im.mode - if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <=3): + if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): raise PyCMSError("renderingIntent must be an integer between 0 and 3") if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): @@ -300,8 +305,9 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER if not isinstance(outputProfile, ImageCmsProfile): outputProfile = ImageCmsProfile(outputProfile) transform = ImageCmsTransform( - inputProfile, outputProfile, im.mode, outputMode, renderingIntent, flags=flags - ) + inputProfile, outputProfile, im.mode, outputMode, + renderingIntent, flags=flags + ) if inPlace: transform.apply_in_place(im) imOut = None @@ -334,6 +340,7 @@ def getOpenProfile(profileFilename): except (IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent=INTENT_PERCEPTUAL, flags=0): """ (pyCMS) Builds an ICC transform mapping from the inputProfile to the @@ -389,7 +396,7 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent :exception PyCMSError: """ - if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <=3): + if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): raise PyCMSError("renderingIntent must be an integer between 0 and 3") if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): @@ -404,6 +411,7 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent except (IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMode, renderingIntent=INTENT_PERCEPTUAL, proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC, flags=FLAGS["SOFTPROOFING"]): """ (pyCMS) Builds an ICC transform mapping from the inputProfile to the @@ -476,8 +484,8 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo :returns: A CmsTransform class object. :exception PyCMSError: """ - - if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <=3): + + if not isinstance(renderingIntent, int) or not (0 <= renderingIntent <= 3): raise PyCMSError("renderingIntent must be an integer between 0 and 3") if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): @@ -497,6 +505,7 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo buildTransformFromOpenProfiles = buildTransform buildProofTransformFromOpenProfiles = buildProofTransform + def applyTransform(im, transform, inPlace=0): """ (pyCMS) Applies a transform to a given image. @@ -546,6 +555,7 @@ def applyTransform(im, transform, inPlace=0): return imOut + def createProfile(colorSpace, colorTemp=-1): """ (pyCMS) Creates a profile. @@ -586,6 +596,7 @@ def createProfile(colorSpace, colorTemp=-1): except (TypeError, ValueError) as v: raise PyCMSError(v) + def getProfileName(profile): """ @@ -612,14 +623,14 @@ def getProfileName(profile): if not isinstance(profile, ImageCmsProfile): profile = ImageCmsProfile(profile) # do it in python, not c. - # // name was "%s - %s" (model, manufacturer) || Description , - # // but if the Model and Manufacturer were the same or the model + # // name was "%s - %s" (model, manufacturer) || Description , + # // but if the Model and Manufacturer were the same or the model # // was long, Just the model, in 1.x model = profile.profile.product_model manufacturer = profile.profile.product_manufacturer if not (model or manufacturer): - return profile.profile.product_description+"\n" + return profile.profile.product_description + "\n" if not manufacturer or len(model) > 30: return model + "\n" return "%s - %s\n" % (model, manufacturer) @@ -627,6 +638,7 @@ def getProfileName(profile): except (AttributeError, IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def getProfileInfo(profile): """ (pyCMS) Gets the internal product information for the given profile. @@ -647,7 +659,7 @@ def getProfileInfo(profile): tag. :exception PyCMSError: """ - + try: if not isinstance(profile, ImageCmsProfile): profile = ImageCmsProfile(profile) @@ -660,7 +672,7 @@ def getProfileInfo(profile): for elt in (description, cpright): if elt: arr.append(elt) - return "\r\n\r\n".join(arr)+"\r\n\r\n" + return "\r\n\r\n".join(arr) + "\r\n\r\n" except (AttributeError, IOError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -677,7 +689,7 @@ def getProfileCopyright(profile): is raised Use this function to obtain the information stored in the profile's - copyright tag. + copyright tag. :param profile: EITHER a valid CmsProfile object, OR a string of the filename of an ICC profile. @@ -693,6 +705,7 @@ def getProfileCopyright(profile): except (AttributeError, IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def getProfileManufacturer(profile): """ (pyCMS) Gets the manufacturer for the given profile. @@ -704,7 +717,7 @@ def getProfileManufacturer(profile): is raised Use this function to obtain the information stored in the profile's - manufacturer tag. + manufacturer tag. :param profile: EITHER a valid CmsProfile object, OR a string of the filename of an ICC profile. @@ -720,19 +733,20 @@ def getProfileManufacturer(profile): except (AttributeError, IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def getProfileModel(profile): """ (pyCMS) Gets the model for the given profile. - + If profile isn't a valid CmsProfile object or filename to a profile, a PyCMSError is raised. - + If an error occurs while trying to obtain the model tag, a PyCMSError is raised - + Use this function to obtain the information stored in the profile's - model tag. - + model tag. + :param profile: EITHER a valid CmsProfile object, OR a string of the filename of an ICC profile. :returns: A string containing the internal profile information stored in an ICC @@ -748,6 +762,7 @@ def getProfileModel(profile): except (AttributeError, IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def getProfileDescription(profile): """ (pyCMS) Gets the description for the given profile. @@ -759,7 +774,7 @@ def getProfileDescription(profile): is raised Use this function to obtain the information stored in the profile's - description tag. + description tag. :param profile: EITHER a valid CmsProfile object, OR a string of the filename of an ICC profile. @@ -813,6 +828,7 @@ def getDefaultIntent(profile): except (AttributeError, IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def isIntentSupported(profile, intent, direction): """ (pyCMS) Checks if a given intent is supported. @@ -862,15 +878,17 @@ def isIntentSupported(profile, intent, direction): except (AttributeError, IOError, TypeError, ValueError) as v: raise PyCMSError(v) + def versions(): """ (pyCMS) Fetches versions. """ - + import sys return ( - VERSION, core.littlecms_version, sys.version.split()[0], Image.VERSION - ) + VERSION, core.littlecms_version, + sys.version.split()[0], Image.VERSION + ) # -------------------------------------------------------------------- @@ -880,14 +898,16 @@ if __name__ == "__main__": from PIL import ImageCms print(__doc__) - for f in dir(pyCMS): - print("="*80) - print("%s" %f) - + for f in dir(ImageCms): + doc = None try: - exec ("doc = ImageCms.%s.__doc__" %(f)) + exec("doc = %s.__doc__" % (f)) if "pyCMS" in doc: # so we don't get the __doc__ string for imported modules + print("=" * 80) + print("%s" % f) print(doc) - except AttributeError: + except (AttributeError, TypeError): pass + +# End of file From a97e5039d83bdb862b577196dc0736173f15f09a Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 14 Apr 2014 12:51:12 +0300 Subject: [PATCH 02/33] Remove unused _binary import (plus flake8) --- PIL/Jpeg2KImagePlugin.py | 41 ++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/PIL/Jpeg2KImagePlugin.py b/PIL/Jpeg2KImagePlugin.py index f57f4a784..ee8fc46aa 100644 --- a/PIL/Jpeg2KImagePlugin.py +++ b/PIL/Jpeg2KImagePlugin.py @@ -15,20 +15,21 @@ __version__ = "0.1" -from PIL import Image, ImageFile, _binary +from PIL import Image, ImageFile import struct import os import io + def _parse_codestream(fp): """Parse the JPEG 2000 codestream to extract the size and component count from the SIZ marker segment, returning a PIL (size, mode) tuple.""" - + hdr = fp.read(2) lsiz = struct.unpack('>H', hdr)[0] siz = hdr + fp.read(lsiz - 2) lsiz, rsiz, xsiz, ysiz, xosiz, yosiz, xtsiz, ytsiz, \ - xtosiz, ytosiz, csiz \ + xtosiz, ytosiz, csiz \ = struct.unpack('>HHIIIIIIIIH', siz[:38]) ssiz = [None]*csiz xrsiz = [None]*csiz @@ -48,13 +49,14 @@ def _parse_codestream(fp): mode == 'RGBA' else: mode = None - + return (size, mode) + def _parse_jp2_header(fp): """Parse the JP2 header box to extract size, component count and color space information, returning a PIL (size, mode) tuple.""" - + # Find the JP2 header box header = None while True: @@ -76,7 +78,7 @@ def _parse_jp2_header(fp): size = None mode = None - + hio = io.BytesIO(header) while True: lbox, tbox = struct.unpack('>I4s', hio.read(8)) @@ -90,7 +92,7 @@ def _parse_jp2_header(fp): if tbox == b'ihdr': height, width, nc, bpc, c, unkc, ipr \ - = struct.unpack('>IIHBBBB', content) + = struct.unpack('>IIHBBBB', content) size = (width, height) if unkc: if nc == 1: @@ -112,13 +114,13 @@ def _parse_jp2_header(fp): elif nc == 4: mode = 'RGBA' break - elif cs == 17: # grayscale + elif cs == 17: # grayscale if nc == 1: mode = 'L' elif nc == 2: mode = 'LA' break - elif cs == 18: # sYCC + elif cs == 18: # sYCC if nc == 3: mode = 'RGB' elif nc == 4: @@ -127,6 +129,7 @@ def _parse_jp2_header(fp): return (size, mode) + ## # Image plugin for JPEG2000 images. @@ -141,16 +144,16 @@ class Jpeg2KImageFile(ImageFile.ImageFile): self.size, self.mode = _parse_codestream(self.fp) else: sig = sig + self.fp.read(8) - + if sig == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a': self.codec = "jp2" self.size, self.mode = _parse_jp2_header(self.fp) else: raise SyntaxError('not a JPEG 2000 file') - + if self.size is None or self.mode is None: raise SyntaxError('unable to determine size/mode') - + self.reduce = 0 self.layers = 0 @@ -177,13 +180,15 @@ class Jpeg2KImageFile(ImageFile.ImageFile): t = self.tile[0] t3 = (t[3][0], self.reduce, self.layers, t[3][3]) self.tile = [(t[0], (0, 0) + self.size, t[2], t3)] - + ImageFile.ImageFile.load(self) - + + def _accept(prefix): return (prefix[:4] == b'\xff\x4f\xff\x51' or prefix[:12] == b'\x00\x00\x00\x0cjP \x0d\x0a\x87\x0a') + # ------------------------------------------------------------ # Save support @@ -214,7 +219,7 @@ def _save(im, fp, filename): fd = fp.fileno() except: fd = -1 - + im.encoderconfig = ( offset, tile_offset, @@ -228,10 +233,10 @@ def _save(im, fp, filename): progression, cinema_mode, fd - ) - + ) + ImageFile._save(im, fp, [('jpeg2k', (0, 0)+im.size, 0, kind)]) - + # ------------------------------------------------------------ # Registry stuff From 92eea7a9cc5022750e82ba07da96612bdea03f72 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 12 May 2014 17:32:04 +0300 Subject: [PATCH 03/33] Tests for ImageDraw --- Tests/images/imagedraw_arc.png | Bin 0 -> 284 bytes Tests/images/imagedraw_chord.png | Bin 0 -> 326 bytes Tests/images/imagedraw_ellipse.png | Bin 0 -> 491 bytes Tests/images/imagedraw_pieslice.png | Bin 0 -> 405 bytes Tests/test_imagedraw.py | 106 ++++++++++++++++++++++++++++ 5 files changed, 106 insertions(+) create mode 100644 Tests/images/imagedraw_arc.png create mode 100644 Tests/images/imagedraw_chord.png create mode 100644 Tests/images/imagedraw_ellipse.png create mode 100644 Tests/images/imagedraw_pieslice.png diff --git a/Tests/images/imagedraw_arc.png b/Tests/images/imagedraw_arc.png new file mode 100644 index 0000000000000000000000000000000000000000..b097743890cb1907122e53a98231e8887d791d12 GIT binary patch literal 284 zcmeAS@N?(olHy`uVBq!ia0vp^DIm3c2+)C>xpS`>3f5(Zs9FD75X*zkc7DBDk+omSCdxl;y1HgSsJzU zmEpZza$2#*Ruz7e8#YW!Ufg!%%NmQF_u8+!e@Xt)YcH*9vF6qLSDa@!ATD08i}~^A Vp2dg61W$kjJYD@<);T3K0RX^ebT$A0 literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_chord.png b/Tests/images/imagedraw_chord.png new file mode 100644 index 0000000000000000000000000000000000000000..db3b35310235b1c7ab6e368d08f3bf89b9836777 GIT binary patch literal 326 zcmeAS@N?(olHy`uVBq!ia0vp^DImUz33VYkl1=nX|dqBVT;otL=PqD@Cub z;$2<(Q7d|t+$rnTn(Kcp*>>*yf#>4CDh^JY@$Oe#xmVO0!f&cvEFGQ N0Z&&ymvv4FO#l*njdTD2 literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_ellipse.png b/Tests/images/imagedraw_ellipse.png new file mode 100644 index 0000000000000000000000000000000000000000..fb03fd148597a95bd4c446243b064e91561973ff GIT binary patch literal 491 zcmeAS@N?(olHy`uVBq!ia0vp^DImVh<$#e#iZH>9#NfrDIbrR>YpU ze7$YWlI^<={o3`zoN@0bj&rel?AHhzp0!dkZr|!Ur!TXt|4fOf{JhIYI({vYbH8n~ zc*b*c!6RReTsp#|>pefj$JfVGcADzSxpw-g5y}5e?m2%e=DqrGY51nd$yPnSC9%7v zEtR#4I=7B9`^|jce)dfVy-RF;Wee0^^mI+TdYdu*=+ur~iRGIwT?vrAreSw4a`C5x zryunVqRx7`>s@dDGT$MBb8F(-V);2!LqqKjtT#z;Ewn8;%yP&}VGRnQF@K7lS*QNm zSdk>^3}sWN-MWEb#G4)TthcJgG5d9|o^KVGt;09S8ivU?I=I@PlRl V%h(%|l_|GCVxF#kF6*2UngEBh-Om63 literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_pieslice.png b/Tests/images/imagedraw_pieslice.png new file mode 100644 index 0000000000000000000000000000000000000000..1b2acff92ab9aa263ce85ceb1a37493d4cbef6cb GIT binary patch literal 405 zcmeAS@N?(olHy`uVBq!ia0vp^DImmi_QNfYxaH53Vj{XAGUgN#YVIFC)qlsvNwOXirup8(c8%OTW-&O7bl%x zb9aX7OqFSEUrq^oubqDX-&Cz>|MDXD^3Br?du+5*=-8*|9?s;GxrWbru1ff?+p^I{ zuQM|_H>Z5vnT=<_gy8GO{EpOZxvKrC)xJJ*r^PnPDO_VdQk8HsVzb8!n${V^hH)%9N+zbw06{%>NP@%}SSuANIouO%;tm~~G(Ovm Date: Mon, 12 May 2014 19:55:18 +0300 Subject: [PATCH 04/33] Make sure bounding box in ints (for Py3) --- Tests/test_imagedraw.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 11f4fcd91..9904457af 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -7,10 +7,10 @@ from PIL import ImageDraw w, h = 100, 100 # Bounding box points -x0 = w / 4 -x1 = x0 * 3 -y0 = h / 4 -y1 = x0 * 3 +x0 = int(w / 4) +x1 = int(x0 * 3) +y0 = int(h / 4) +y1 = int(x0 * 3) # Two kinds of bounding box bbox1 = [(x0, y0), (x1, y1)] From 6b274b4c5e22a06ad72a682f7cb80a8b541dd87b Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 12 May 2014 21:45:16 +0300 Subject: [PATCH 05/33] More ImageDraw tests. Some may need redoing after issues #367 and #463 are sorted. --- Tests/images/imagedraw_line.png | Bin 0 -> 286 bytes Tests/images/imagedraw_point.png | Bin 0 -> 124 bytes Tests/images/imagedraw_polygon.png | Bin 0 -> 292 bytes Tests/images/imagedraw_rectangle.png | Bin 0 -> 228 bytes Tests/test_imagedraw.py | 88 +++++++++++++++++++++++++++ 5 files changed, 88 insertions(+) create mode 100644 Tests/images/imagedraw_line.png create mode 100644 Tests/images/imagedraw_point.png create mode 100644 Tests/images/imagedraw_polygon.png create mode 100644 Tests/images/imagedraw_rectangle.png diff --git a/Tests/images/imagedraw_line.png b/Tests/images/imagedraw_line.png new file mode 100644 index 0000000000000000000000000000000000000000..6d0e6994d861246c94a905d395b683775489ed09 GIT binary patch literal 286 zcmeAS@N?(olHy`uVBq!ia0vp^DIm(?a5DGbZ$zeim|1j!c@7cxq%qP7CDP~|u_;jC{;ef806_|%a9N^|HJI(rAL?`wsNYvBS&t;uc GLK6VpyB)Xy literal 0 HcmV?d00001 diff --git a/Tests/images/imagedraw_polygon.png b/Tests/images/imagedraw_polygon.png new file mode 100644 index 0000000000000000000000000000000000000000..5f160be76ceef3d85c7092a63d87e48115ec3303 GIT binary patch literal 292 zcmeAS@N?(olHy`uVBq!ia0vp^DImw+vGjmJT^EHnX^geT_FfuUkUHZNI{sZ}!Rk|0m4{9vEKINY9-e)hTs>plK zf0wzoVExtz&HLGxuB(RaJN`74*Yta-VVKvqS^Za|;!cOWF0GApUKv-OyfkS2(un&p zj{=rlZ%gGBt;+45%Ct8u`ENz%;hTHQ^s+5>T>lqgxjlUMmyq|u{M){)zW?9dcGH?U z5nB95tu}?MJNE0X`Nu0kH}|b+_D;=Tv)TLZmtXf^?!N!LuzFf(O0aa2sAg&W`hwcs f8mvISp@4!0|C_PVOkF`octGNwu6{1-oD!M-=- z>O8hT9}YG)8MEq~w&nw>0Rg?=IellJ-B`Bs_km6zUKttvYn|@ihKNQQ!h`vh`P05b c>}QaB%D Date: Mon, 12 May 2014 23:27:02 +0300 Subject: [PATCH 06/33] Test experimental floodfill() --- Tests/images/imagedraw_floodfill.png | Bin 0 -> 232 bytes Tests/test_imagedraw.py | 16 ++++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 Tests/images/imagedraw_floodfill.png diff --git a/Tests/images/imagedraw_floodfill.png b/Tests/images/imagedraw_floodfill.png new file mode 100644 index 0000000000000000000000000000000000000000..89376a0f01585431b6dfcdce1ab78d41ad1ae0c0 GIT binary patch literal 232 zcmeAS@N?(olHy`uVBq!ia0vp^DIm>$A6c=E^p z_-RJRvt}Gvu!@~0CVtOxMxZJnIFS6j;PtMyWbfxMrrtk$=Dg9r6Ysd~H!l?z(55Af ikWc^j+E5$nh9kTQ1uU8CSMGTP67Y2Ob6Mw<&;$VOggeUs literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 727275c2d..73617b97a 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1,6 +1,7 @@ from tester import * from PIL import Image +from PIL import ImageColor from PIL import ImageDraw # Image size @@ -219,4 +220,19 @@ def test_rectangle2(): helper_rectangle(bbox2) +def test_floodfill(): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + draw.rectangle(bbox2, outline="yellow", fill="green") + centre_point = (int(w/2), int(h/2)) + + # Act + ImageDraw.floodfill(im, centre_point, ImageColor.getrgb("red")) + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill.png")) + + # End of file From 5b55cb72d33c692f1677341311ec3df05dd0b500 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 12 May 2014 23:33:05 +0300 Subject: [PATCH 07/33] Test experimental floodfill with a border --- Tests/images/imagedraw_floodfill2.png | Bin 0 -> 212 bytes Tests/test_imagedraw.py | 17 +++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 Tests/images/imagedraw_floodfill2.png diff --git a/Tests/images/imagedraw_floodfill2.png b/Tests/images/imagedraw_floodfill2.png new file mode 100644 index 0000000000000000000000000000000000000000..41b92fb75a032515147d1d63d27f45d61d85f69b GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^DIm?LEc!(!8PgVbG9L%=Jzf1= J);T3K0RX!!He~<+ literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 73617b97a..2a1f963c7 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -235,4 +235,21 @@ def test_floodfill(): assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill.png")) +def test_floodfill_border(): + # Arrange + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + draw.rectangle(bbox2, outline="yellow", fill="green") + centre_point = (int(w/2), int(h/2)) + + # Act + ImageDraw.floodfill( + im, centre_point, ImageColor.getrgb("red"), + border=ImageColor.getrgb("black")) + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_floodfill2.png")) + + # End of file From 60b25701332bb2056e3903761f456849186f046f Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 13 May 2014 14:43:48 +0300 Subject: [PATCH 08/33] Test ImageDraw's bitmap() --- Tests/images/imagedraw_bitmap.png | Bin 0 -> 2127 bytes Tests/test_imagedraw.py | 14 ++++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 Tests/images/imagedraw_bitmap.png diff --git a/Tests/images/imagedraw_bitmap.png b/Tests/images/imagedraw_bitmap.png new file mode 100644 index 0000000000000000000000000000000000000000..05337b693928aa27ede94ddf8aa1b4afc534ff8c GIT binary patch literal 2127 zcmaKudr;Eb7RP^@CC6j=2+6DFBPgwlMoLPC*~KvN_(%>Tzgi>yS|CmJzIj7)3jly1xD)?4opSFZ%QtGL$W(A4S>o-O&f52I zJLT~to9+TKdYxQWVhSnOj}3*RMfSv zA}P78VqCOQF|G(jOiQIP6HZT*!n7Ts$w>}Jzb!e4(JHjY4GjB~4Or*qe}wM3JV7{j z4!!Vh(RB8a#Uxe!NxR$ZJA$uyH6?iG&kLV@O3CX7iT_l({La1S0dww9_2lSHzfhrR zr_ko@aACYzUcB_=h!KCyK0VfRPF!8!UR&q^Guv_5On}hE3?Ottp`GW0z;%I5f6IQy zzm%*!YQ*0^dhm{O^3t2-DM7%)(wu*iuY zgz3Mpt*t4gt-;bKl(XPI|A(iWM!Y0zPS?!0orDJ3m9mxG)>Tj<6q>$ry*+K?c@j8IPmnb7EPLe0KxPbdlZ{=UhV7T<6uV zoKAA9&6R|NBI8|{0WRELIA9&`)e?|NCSGUG9yKYRqqYp~bg*iG3yTPY@%Kn+>wSz} z`Y#hY;6iSFv_1yJZfq1qO1N-o;%K;VN=4Lk&zB|XmFYGx76&tIX?JmOxO~IO6^(AT(V@Mr4rt!#Z20$b zFJAMcq`|kq+{O7tu}>8mHyd=UT8b3qFy6+^+fEXkNM=m-ZVJekMQ)A;?_u3-ZjEd?99&~)b2uB&GpKkWrH$|-?6C2fG zqPq=RIFN7Zgy>hB!jUTVUmmx7EtN_Y3JcwBztjycEW|ig;PE4R>~Vu!tm6&l)a~%R zWp!Q}K!JrTk0Mh&KW;fmt>P$eVyr4ek!?h*-atKrQc_Zyy>@pY0n$H)$UA3OCZ3CZa;9xOuP-bsE6dHz zEz@#mmL#3Yn^MTUjdBp#rLb)1urjYY-5;BYvK38laP@%2;J z_4Na|qA66SrPizz9na<2)VcW)87eC&?zzjUs|n)rBmMc9`Q4Gpzj5NiU%^DD!{ z!{?o#G)r2EXWf|=4CqilpW_B16ZhT3%6PZ~BYHlam0PkA>TAhla!|Nq#a^l(@IsBP zi@2h?TG6DvvgQ4!#9)P7{%THD=RepkTg=MEW>vpMq?}-AL)BbMYpZMe#`DA& z84rdl@thefwhjmgVAZ>%d=B{vB?ju#QqXoFO+oy}GDQD<`&lvl%E_?8KYQOkOSU+c z7RA~pRsn$F`2%!yZyBK6ms<-H?8?rPNL`obnCc3I1$QLOY?=>pGcE7?Ka|Vm$3xmB zFkzkIxe*)s*keRFFKIBI8XqSDKs=IFKajmdN#?rSHfv~J4NX?#+`=CRM z^|Um*LmsuaJDKFKuB;GB6Yq9Cj$MS>U_txo{x)Dl|47V8;@b%OTZfW@r7E;xj2dvy zIt;!3e&L-;4R!0J*F5d$DEF+>%mBuF5xOdi6}6vv@wG!X5C%s%s!XKa<|xes^L(GQ z(kaxJRF`UT@zKEvUl^1mMYa&&?zMiwQd{AHGGSxmcf^S?#(F2dYVK-kDxyE)B&NpG zFn0@Joc)e^%)DR=Fwm8oL{Xxcu_ck-l0T5%czm#Q6VkbA4Jkj|HTK3{*A3IqnUrKE zSVmibv<5cw)1GRq>`pJNuKC}{{qN-cKawNf1ahkTB$z@01oSOf`bXk{5P{v B0-gW> literal 0 HcmV?d00001 diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 2a1f963c7..0a9366928 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -71,6 +71,20 @@ def test_arc2(): helper_arc(bbox2) +def test_bitmap(): + # Arrange + small = Image.open("Tests/images/pil123rgba.png").resize((50, 50)) + im = Image.new("RGB", (w, h)) + draw = ImageDraw.Draw(im) + + # Act + draw.bitmap((10, 10), small) + del draw + + # Assert + assert_image_equal(im, Image.open("Tests/images/imagedraw_bitmap.png")) + + def helper_chord(bbox): # Arrange im = Image.new("RGB", (w, h)) From 2662c38f5c5b7b4da8007f51c6020766d9e3f63f Mon Sep 17 00:00:00 2001 From: wiredfool Date: Tue, 20 May 2014 12:59:31 -0700 Subject: [PATCH 09/33] Updating docs to reflect current understanding --- docs/reference/ImageDraw.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 68855eb5b..45be43057 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -91,9 +91,12 @@ Methods Draws an arc (a portion of a circle outline) between the start and end angles, inside the given bounding box. - :param xy: Four points to define the bounding box. Sequence of either - ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. - :param outline: Color to use for the outline. + :param xy: Four points to define the bounding box. Sequence of + ``[x0, y0, x1, y1]``. + :param start: Starting angle, in degrees. Angles are measured from + 3 o'clock, increasing clockwise. + :param end: Ending angle, in degrees. + :param fill: Color to use for the arc. .. py:method:: PIL.ImageDraw.Draw.bitmap(xy, bitmap, fill=None) @@ -111,8 +114,8 @@ Methods Same as :py:meth:`~PIL.ImageDraw.Draw.arc`, but connects the end points with a straight line. - :param xy: Four points to define the bounding box. Sequence of either - ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. + :param xy: Four points to define the bounding box. Sequence of + ``[x0, y0, x1, y1]``. :param outline: Color to use for the outline. :param fill: Color to use for the fill. @@ -144,8 +147,8 @@ Methods Same as arc, but also draws straight lines between the end points and the center of the bounding box. - :param xy: Four points to define the bounding box. Sequence of either - ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. + :param xy: Four points to define the bounding box. Sequence of + ``[x0, y0, x1, y1]``. :param outline: Color to use for the outline. :param fill: Color to use for the fill. From 35336e5afd5e3eb9ea2991eaf3b705877c49a173 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 24 May 2014 17:31:37 +0300 Subject: [PATCH 10/33] Test arc, chord, pieslice give TypeError when bounding box is [(x0, y0), (x1, y1)] --- Tests/test_imagedraw.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 0a9366928..21eca5038 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -62,9 +62,8 @@ def helper_arc(bbox): assert_image_equal(im, Image.open("Tests/images/imagedraw_arc.png")) -# FIXME -# def test_arc1(): -# helper_arc(bbox1) +def test_arc1(): + assert_exception(TypeError, lambda: helper_arc(bbox1)) def test_arc2(): @@ -98,9 +97,8 @@ def helper_chord(bbox): assert_image_equal(im, Image.open("Tests/images/imagedraw_chord.png")) -# FIXME -# def test_chord1(): -# helper_chord(bbox1) +def test_chord1(): + assert_exception(TypeError, lambda: helper_chord(bbox1)) def test_chord2(): @@ -162,9 +160,8 @@ def helper_pieslice(bbox): assert_image_equal(im, Image.open("Tests/images/imagedraw_pieslice.png")) -# FIXME -# def test_pieslice1(): -# helper_pieslice(bbox1) +def test_pieslice1(): + assert_exception(TypeError, lambda: helper_pieslice(bbox1)) def test_pieslice2(): From 8f92562ec353e7a2efeb96fb85aa5d76f3270492 Mon Sep 17 00:00:00 2001 From: hugovk Date: Sat, 24 May 2014 17:36:31 +0300 Subject: [PATCH 11/33] Remove old FIXME comment [CI skip] --- Tests/test_imagedraw.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 21eca5038..2e0cc07c0 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -53,7 +53,6 @@ def helper_arc(bbox): draw = ImageDraw.Draw(im) # Act - # FIXME Should docs note 0 degrees is at 3 o'clock? # FIXME Fill param should be named outline. draw.arc(bbox, 0, 180) del draw From e6908401d4935ebe368c15088e56f0bbf9687634 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 26 May 2014 19:19:27 +0300 Subject: [PATCH 12/33] Add config file for coverage.py to exclude some common things: http://nedbatchelder.com/code/coverage/config.html --- .coveragerc | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..bd93c4749 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,11 @@ +# .coveragerc to control coverage.py + +[report] +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma: + pragma: no cover + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: From 268c419d7c2beeb2fdbfdbdee27b7f4026778522 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 26 May 2014 20:09:12 +0300 Subject: [PATCH 13/33] Simple spider test. Test image created with Pillow. --- Tests/images/lena.spider | Bin 0 -> 66560 bytes Tests/test_file_spider.py | 15 +++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 Tests/images/lena.spider create mode 100644 Tests/test_file_spider.py diff --git a/Tests/images/lena.spider b/Tests/images/lena.spider new file mode 100644 index 0000000000000000000000000000000000000000..df963c1c3cfa6b95f7337611c7b932edaa57b4a4 GIT binary patch literal 66560 zcmeF4XLOZS+OX4m@4fe4Nh1XIPN9SlAV8=hBJzq3I>;acE))a=M4BR^G7ci64kDur zGKkC|BGLq;gES#RC?SLp0)+5g_j68^@tyhq{digHUMHlS^PK0p`@Z)+78Wm-SXfwS z#Qe|rf5(4*`zXKH^|#;r-~WHHl<$fB-@pI61OImi{y)A0D?dor8^baTNxqn>y>zl( z?~sHf>$_V-5|9+_QJbd|+Y0sUPMvh!GdW1C-gnzkr@wnqrN4VcwbwMN_l7j9^L4jX z{IhqEd+Mb(?y2aZchqD@3w`x~wLX~Yh`8!i9v+A%;-wdS?T`2(^0kMa6ei+@`0Bt5 ze*8`V;;+k(hy)sssaZbSB>sA2Y@l9!(jW2DlTQ00-g@)vUWg~JCE|Z2zf^wsI1eu$5rw8hJKUK>Ae9lFU~Pw{crOE$XcrY0x7^t`3M zSk$bx?y}Qcclqc=pTy|keaSp$63>yK+nW>gzN7KF_D&r4@3#`m_e>%`_tL3)!_7oJ zWL1oA&&ky*>-y^X{v-6N{X@8hp*(MdK6&T7s&;*#7WH=5EtjtGx?%c^d%Ifa<)Tl; zIP1I~&N{b?o3`=u_zPcr$O+_}^>1E${(l6zv>6IU( z!d`SBO($PW;aY@!qM-xlf>QLkAkl$TGyekk=fXY4H_SzZy$|d~e0BY|eu%F=5ao{q@m~B9 zZ?l-Vo7f9~Ve5+sYY{J`gT4G;zV<@`5aDln{#72n2LJLS-rTzm9q`ff2XQ^8ee~e~ zFP+_A_`AX175PCBegZ;G2j_`MazYEvujvfnt z7x*~vT=v+E*n6(T8xuA5e zI}QGwu>U;x3;T2=Tqnm{!~Zb+Pr?2w(hC3khV2(SwZQ)d{BOa(4gQwgb9=;@d+lcE zK>fGwhMkM8cSr|!6MNy~qy3M2Bffg$ia^94@z(F%^+Dk13Qt#mMEr!wSBO2=f9s8S zJ@n{l{<=e_OnPO~FCTQvhx_D>2!9dL1y6JUKXBT^xUZGBUC{yI?|@in(f4AfR&3D$ z{y}=OVLdY^y)8% z^S%b_)xlNzaIJ%`cDV=n(+Y%?YypwuD<4|UwGi4!?!!&qnx-_7yibL_u-5< z8SiQH>#neOgngiHKINtDx}8;6u+5bJ`zP$9tE%@J5O9Jk~-vah_h!Y%K z5Ld(v8~6Uj*2Uk~f9tO&dW-lO%!U2FJP|)6P(OR$UvK!_$1H(x6W`#6h<^y+Z~PJY z+YY_yc=Y0XqDS&G`5n{mnO;YJ$Mo|FmwdSYrub5RM0}|!mIUu}hp&eg9ayr_5&l+M ze4Xg|)?HTc_u}W#@J}}LPlkUA{8Qnd0(%khrJ@VJ>O<|FcfedN5s;&g%vLMEEC}ZC~ua!}gQlp91?FGyiP(XX){u zrNE!xj}LKTro#ZDH*IV`npe(SZ&d z6}#-vwk;)?{De@Aq~{~?k8oBr0MHyvNgV*nz5Cla79 zp7ce$5ikDlZTOarD|~qLLJvIADR2CWDV_xH^MtcIzSo(1Z-*XO>SCw6yieltCrONjB6Q1le%i@@ zk6Q1rP;Jh6Svjm3uZrhB^N{@~a~-0e;-`dtDv}QSTr>YHJ1O zHs`0uXL%z&NPwTp}pfi`@AEkBdIG8WtG;GYBk4EXOpWEqHPA&!oN~)3AfbSBU;py_16018&+JG*pf*C zFPL;7c%K{ozz5&p5BpesCf!@FYr3XNd(DG;iJH^xd9}0CEH(45uftz_Klw*?+An^} z#6CmMu*gD$e}=|iz(3v0Uwl9)KEP6o?-w0N-cgU-F!OIU{9^RmH{su6`2R^^mcQZe z#&x?HHYRo^Ih`q{`{LtGvf*>FaS_vBH2?^k`9kbvG9{N-mN-iFR?_*{IB2Y>5n#Dn>}9fAGF|GygPl0I~lJL&OKv-Z9034)!Mga_=SgN;YD^hMQ4_4oEKG(~zoH z#`e^q%ZKaW>jU++H-@7F(R%Id+iLCXJ1V!`hHG;~oQRh$T%QxyCw|Bc_C9)Td?4JS zv|H*o%Hm(|s_?Q`)Zx}gRN{~0)Wjv@)TH($N?zL(qvZNbeoE}##6HL1@76n==Slm8 z|K0X9?e%^*Vnq&oOkFHG3;Qd`P59q7*q?E4Bv+|7_=nHDrzVA2aea2iHBR()<9gi@ zIIR`&guRFt?7b0*?_&GH-VdAiL43{p{mkq=dE|F)1|oiD{_e1Kdek#R^yD7Zdi#6V)Jwkj9480fr|@^=UOI7o4qTVydCsu+hFJjY4=IZm$H9IU zHa}f?S^p2*-%`h`7OKzQeGdL|eI2%+imgk&*U8{6I^fn@B$MY#(ZzF<^*i5W>bvdf zun$6{2D$+MA7OtAxu)KExmBHUZ$X;ie?y(Tb3=vCybXU#Jz}{H;=r{!BQAzKOFsJS zDNn@15Ovm5tFzqy6yfg&hd{&jy{vt7?LWK`Uqcc%d+Wr_9y;|0FMToCPp^F3Q_tH& z-s|UOJa7HC4o2KI^%$uW;pi z^1G(LmD*HvKzzVtMAO`-@1CSh$+TP%6y2rJ5I`|9wMDl5Q zlo*g=_+N>^COwEk55m!dXua`tFI_c#ls+0gR4;jcxNe{hTb6cNRr)*V)XyF$|KpNJ zi%!}TlYGeW{q>9pJH7m^i)!-!)T;M_o=}UPeqJ3I^`iQ)d?xH)QPYxUk^jF4fAL9j zZIa_>!#*4KCc9tfm7~{rWx_v0*I1{T`DYmX@3sf(*zyOkKY<*B{W-OF&kgwBg?%&9 zpbByt%=~Yu$savX|IS}@z}d{)1Lp2#K@aYG!e0DvN1kW0M=xt{Ll;c`-emLhu6pZv zSN-&+dM|X#!{8~k?xfQW^lqeo6NGppu10MnKRcL8eoSv5@Pa?QgqidSBoCFkBLLok zx-{7naY5V>4|EH?%J$Kl4@u9$&F}#qmOIb~=-_V;e{UFsoB79+119}a1IT^;RmA5D z^CaqM(Sal+nfy8lNq~j)h9c2}2y`Gq&pDT)S7i*)m0t|k+Xg(UxBRoeKB`0XO9SfE z!TI;et37#LH`v?2-UIgj`0BH2MB$6-eC$vanemibxqYfS{QQe*=O3mipR{RezW-D; zsmo-Q*p|p^N`EKYsPRnvGY#(Rys~*rCwEN$H~vrWNY%S70}%^7r`vw=i6gMDLvEY- zH>xM6*8jpkH>~yF_?P=poBFvJV&d)wdy_nS-yQD4U;M8rCo{!zQ#_aWZOYjsZ}ZhX zEB%o`Y{H9vhC4CYgFc88xsk12l25-bpFBO^9&yme8yxw*8$b6UFZMEOxufJ2N38?t zo4KF|0qBIp(ja&XcVGO9hsn2G^)hs9{!#j9?ykI^1FvWIZ~V9J>fj%Z{l~&ze1PbH zDaQZGUwpoNFB$$O5gpijHJa`&?5Z^+5veJu3;zSJjpQne;{@fX{*P%iGiNE;&NOFJ}_{YIt`aqKBcj$ny z|1EzL`@L6V@U2lu1aUST8xBVYLdXfC;Gczm8bB{#prHq%16zBB!>dV+UwlcOim}%5 z2V9BwXQ}UI;q&{z`WbTbS+Jj@w&gA~3wi&z=M34FJ01S%*uV7mrPj-Vz3@+leGC!} z`xLYNr|Vcd^dLGD{wc8U;C~4FKVi22n=1asO*8)+@Na|vZTPpt-SH8V}yL^zOWf;nKeqk)FQvdj0g+1OD`~1C8FsI zjNa9z4Eht@5c1ZlBBWHmV^gME`)hulr`=pb5i9h<+0ctMn_ch^PWXjDe5v?(^8FZc zhBzOceoDk!cYoAdFR=2!-?->e`yCKl?B5cxAP10j0OF~1AdXa0D zw)*P>Vyz>!x}$DgvRl=!ehT(OsqJQ|?&toXLWjHt_gB@lk6%+x{)<)B$~o$Ve?Di3 z)s>gvpTlcN?9ak?ny%U@6siYpDR7yt0&S+ z7w^i@PyXnucO*OW+PBnkH$UQn#P~pL-Nb*sKWhR0-g>01C*n>G?##U>|97?1$vYm9 z``lM~AK4oG7kv`?3;&4+qv0P1XORv)kVpAH>t{&aGjY_>vG9+EeP>6yD-_3p?c>R|8J6?ROnSE*J#Ge@~h zp91%V$jh*wj=ZX-9C}3sfBJj1@Ai|(AK`CeKhUic_fYIV4)*j5ygc->*8&i+|4jI2 z!#|7roo(hX@!#4U|M$cG7;@Rn|GMG-rA`?$<`(wfrsf~D#P;ox4*xGcz>W9ij*#;e zdy#9?bE6iQe%wTFQ~zFUUF`gDfbb4Le2LdSh{SE~P4`f};?oFnrciRFF#TR3J~t@kmZk$iq$K!2qO-d%OPXK(IxAvzFAPVG!gu%&jkC$@XS-xr%_ z4Zz=nUc5Wvsux7L=%d?&J^7xiE%i?u{O`cu`oHj}pWAF=ZDJk+_h^Lmv!jvlkA^=r z|EWlw*f~;rY&5ZN^$z1c3;$4J0DW(I9RA0HSP$x^4{v+~{=;=;-s5`SQ=_w}8DZ;s|$B{x@$A`G2u1V$y-qWZ`e>#ZL5=eB1-u7k?TC|1j7G8SEGJ z_RuCFZtQW?v#HJ6c2c*wMd(sz`ey$~((@{lbo%`qJ>qT_l5TuG@$)YFfK@ksUZgL6 zS*#~GR1j;s>5{+q#Se7H2NaPT*puhnGx`=iE8XGmW^iBqC2MQPoZ;^Te>=oRH&)od zzZL$s4gTS_Ueo|$|51kj7yCEm03G}vW*-6naQH{SKLY*{X8sX+r=Lg|_g7*-H2j0P zM+xxvf`1r3s9HCK4A)0zjMfuxKB~QD+*hL(Qp-=Mh51kw_|1GZ`pfrKzvpMd|4m}R zRQNw-*#6=CC2Gz~vtd7t`u}C+5>-UKmk<9O!w31SwbF^hj;eE;?kI^rlKWNf%_Tp| zLDIEDN~Vt7pH8kB%sQeadC)&q_UAj1!!SOBT;#Q`(GPAmdO^|)h<^KyYG|{-r`nJ^ zI}lqt#NM3mu~tpbt~A*jaU=J3BKHc${&IMJ3%zKsJ$+Vp9ogv4>$)0ip6k5aSpW9M z_q!o}=&m36kEfn-)<-`bAFa=wNFv@RVBg7l_ttD;L$USY9?ks@GGdlpw+OukyKnW5 zME4_j&tbd?a)!r#fv-@}MeXWV`9 zN51g)F?8TedJz1%Pf6HL|0wv!W83-g@1$osQ{M)3C016T16_&zMMw`lsY`FYxw#bn z#pHP1bZS!#>*h6jg?k$H!~<2c%~Egbae;n`tC8d7Zt;c>>wT`Qsg>+kleXNZ|Iwmk z9bmGf@Q;LjJaHz0wbul4)=1ckgup)p{;@EP!PX?dk7PYALN6umw|Wx~^7uW^aM*|P zzQg%_;UB~Ag^`;R8)}2_7m++?v|c~<5$c{4J+f{IjJ=Twk8knyn|wVDnPTLC z1?#68Ip35+3-JRltCkN3AcK|IzSRD+JH_x?c6w#m4{BxEw<>YiefUTG8-J7SXTTtx zco0a8Y=`^j$W}A|I{06K{}t^2D*Wr=FMYt9@V`SJ@PP_;w}HLrfcO9>xQn=(`Mbbg z`gnoZzX$!UHlCMtZR)IWTO0V>>XQXB@8YHV9rHm4{Fq}1L_Bru&pxbkMBo$Xce@6| zKbRVTdsmgE&kZ3*A}*8+(E7Y>F*&D3I#F}pS0x${2SpfGfti`kK}L3?QhFEeiAV( z3OkOXUqn7h-%#on)9=I>EFDs!S)+*}p3?KZA4zb!#W&%=8zk7JR}d_7BTd1NWTQidO>;6C@)6FwYGEErCl=%mvC&>-+qWC|tpFXQcEwv$Q1H-N-v;LdP`fr@RcyELn>sg}$ZC<3#`<>a& zU!lI6kIcZ&?Nn zb^5I;^E+lbqHK8|A><#@|H(08z_iO^`&mXG;OQOydZ~3Q+_xcnk>l`x*#Fa)U@h1b z|Hc2`!~a|0|83zew$H4>Az71=K98`M-n-bo^j%oXh_Qvg4g9U(Z%=ORXv|%lEU-rh z?O^Q!Yg0~evcLzu@Z$UKx**42zjQcKw~UR$2MGTVd;qgcAr&x#zh8yk_eH6mV<}QV zKG#Fr94SEZb#QubWCSr`7(Stzn9!U2ZnO?8eG*^LT~A9oqINE|!0vuh>8JLp=1}tg zDGlVk4JtIq0-KffCgGMqE{6`>mz*ty_ZdmQ&_loU#ue3)@IAdcOL~xzYqj4aKdQhB*64C5uboN^$VCTo(bG)6Hr4-$ompe}*Dbj3K=vUgf5ZRZ^S^(O z{~i2YsP)_tCq(MO05gB|1=^5H)cZl zy;;Y?hkI;{H)L;00`Dsc{vqhFC$XcBImr*z#V-%guk+(QM3Z~PkO#&XoLjx&Z6B$Z z2Z}`LqBGId0n!(WU>-1x{!}OJ^yWQs{jSPm*&EdH&r$0=tv2oa2>!3*@1Mi&pHtb{ zQ<(9YL4SV`*EWorZ-Ht*^eHyKQcX@=t9;yC$bFdUI~GiB?XPoJ++jxV2sO_M{Mb<* zj~Meh6E1}l15&uY9D~2~0gC6!`X9ZOQ>@V!--J(x{R{tx{lCnD+;}Med+;CgKmX&d z*ogFlWG$9?t8Du3+2o0zK2TS&6Y>AjYhSe20si*H06T2m5nFfV{&~`?2}Jzq+xega zKIlL&HC&`VMefuxmRh=h4E{UA==;2PH;2AJAuMzC#Lo+m?yRZwWqq|4{sVOGmLB@! zzCCs0jvAh8u(pdFphHj8=q+cS(33_yMNK(aC;oU&&3JLYDtct68oQ{0TI&?|_zb}Pmy;0ib zrwH9|S-2kSN$=V-8a<#kydOhN8G*i|3)U}F;}62$|6XnC(V1F~8IG5ht4xP#`aQky z`7gr$P1WlCG5p7=@m}NA{6X(y`FA;7(TxMF+h2g! zX>?$>F|$>EMDmYR@=xgtb~0>V)_=X;PcddSQa`)F`}qoy`cL@RBiGFN|HJv;t>5JT zlK+X&5Ac?CK)8F8_ouY0`Y|m$hZXnC0sn7LyfCe`EZR%$waW(n4$QK-!Py%f@TKO8 zMFPo*eDSFf`nX#({6mP50q_rje<=OF&e(1aeVBBs)o_*o8TKRz!_mSSu{U~%r zSVtM`3w$ERbm=goPa9I6ZJxXW3;t5%623l098cFuwSSk`ym zhW~5We}C3?XQP+TDZ6g3VEcc;roW)?V?lp3T}Ky1QM-6@KU0ZG2jTt$wtAdC#u>!) z|DG-u)ES}VsKQ^?1oH3$xrQ!0dn%bYM{hRnF5JIF96`fu&Xk^G3A&WQ8~t+`*8hz&yr(E#rMULj>+u~k>CZp@{v6h1|3Thw!*fLv6C-tGMt}~z8_ryDHgh${nb|xH_mk8yKk;~&_i~Uu8U`u__2Th*ey62qncBmAZQ z6OkOih8ouyaYwA-Z()2-)(U>*FaF<}cq{#N)@OWJtD`R~zCL`rx6v1r-eBmaF!+aK zuj2px@&97~q3{oee?0u9?^}lL_k?{9*o*yF!oC;&zZdpj3I9sH?N$}`-v=G2hJ6)M z&Hwx27y9DE2aqR@KnI5F%ml5ilB0Fo|9pl`A0n=wP{I4?)13;_%a6zD=-^CzKpgy; z2hWa!KlOf<^u>bR1JHpWbb$VUw=BaCeD;;hyCh>{Svs%Y zUyoh%Hf!iFsU5pZn5mZdzDVu<^Fn5M=J6a8)W3syU-W5z=D}{ODOzwFcGwi3~brfMA$EkDZz#%h#EBaRv)CV#Xlue$J3xApa z&7v+yp^qVJ<_f<<{pnA6X$Kc- zfjhqNrysD-!>9pQxM#z^6yM(){*}b>3fPw-<>Y@A}K8n}d&(b4tPQ|aM zz&63)zvi7b*5}?-!`v(BzdwTAPQl*p(K~RZKah$aOy%Cn9F)wfB~UkJ^LIJu^!IAW zg?Ms|xvDL3xT=bprIv1c10O#Jzc>SV3I5L*Ixw%wJ}w=y@8x zU+iD{zvmF?|B3xe{nwHIo&SZuX)V~rtOJ(xL<$jCee`{q5tQD5#0QD}{LF~|w!DTr z{5|0BM1Ngo@WbFQ;%UVFqt+3I?aOS(3;(oa{okIooIva-p8Aje{^1hp_!4q|P3~7t z-dE0g0CC{Hf?)+LE0MnRbMW(nsKH%Pwj78zGu73;rOfa=$$fqc`=5kwe+QXv%yUF$Fx&Ts zDXjU-B{x{A);0Z(InN2yd7Fv(;rM;lljFm=ClUCPX!@6l%n_#Pk=rxKNrXFlc#KDx z&IrT^1tZ%Hh^ zf{(H>`+xEA%u0WB8(SyO-&YEMd-&hP{@c+3StDSk-Cg)Q@F+Qe1_F z^ZUWw9icWN?{jeR!uBn(eP`Bk(#XLp;m8*2F!)Y2wzCX>V<=2F~hp(AQT%U&@d4=_znXL27Hs-nlZC+KiljoBQJi+U% zQWx*J=-C_MvHM`|M=-XZ$lO30>&CH$d^jteI4ga-gXq_f=zz@fA2j$&O*kw)*x>)! z{Ve(vx%$arG9!jBB`1*mm?r-({eM{l+Wip!i}1gU{Y(G%F8==_{H?XA|113;8~ERV ze+2vss6Ve!Z(bxmm}W&~jY!svuX@_SUc>?25&I8dCB}%DBOb(C)iuU-Wl7M zb=+KRzXblJ#9aEDA@us}wXUmH^mAAPTq#mcY^^r;e0tOWtJIm7dg@6ldN3>4i}>D$ z$G+HO6+NOp^ajKyNG#}&tqvqF7{u$<>KP-SAZI8f7ko|048e#K0eax~($h;r2k1e0 zC&E7#y$RzwEXX(B#`jmitBCrnn-S9|-t49~C647@cP2)c!|Z>U>wFX+(1*Qw0jl;N zBh|Kv&r@@}%G}l;==J`I=NONFU!YcupJ(WR8ab63;CWRw>Unf!0c-p9_?A@egUmq% z>87k0Y(EA5nR@iSG-jTXkXVENmhi*$GS0xi4*o~se?Wblc~`YJ2hewB=4+eq?*xC@ z3m|)cg}*O7Bk9iwf5`#G{$>5IWBo_!Kgs`O4!q9n|8Ek{CH9N`-%~aNdr*Jo(-Un} zJzcDbfp&0~H6rG)u=}g*Z4%oLc6Z_XGIQyT4#*m-E9|BA4}yiaRwM5zHBx$v!PtH= z{L6UXMXc#&(fiHRwSoEc{z~ZkDSCcNZ*1?%JeNi)^n#x%(18m0SHNEK0?7|6sS&F1 zyS>Tr`lAOXzaYJ##vM`{^wmpsf98hz>31#+*3I{;Stq`#4sX1#M$(6xxjPZxZCWR( ziB6=C77c&w|Nb>{ze%jGr7(lLRJlEUN>v_xly&Vg`p!!G-x;D;+K%G2W!@WKaQ}DY zeSc9at`Ae2o5!k^GncB+6C=p^-iGs3`aN^`*;Hx)S?ixgJ~2V_l-;W`;74|8xPu4H|DU~>xg#JdG`5%S?ndVgHuc|8@9F-N#xP{(qTUE&LPUe**p%@Ri@W z!S6U?|BcKIH1f3{{Cy1m3%+*ZcbL5~_)8wZtoX=!B{Lf**-sPwb_M*q;_H&&&%F9B zS=%o*_|NuZFOGK+?8yT^>yC7zhn}xTe$b5`pQ87#4F0PcdvpJ*b$Lr~q$fInUr6YM z59meSMC{+upL;ieKI{Pe`5{5)I4d%T+`C?NqY2`$>jWL#QhZ5 z$MW2f+^;xvG+oaecZK@@C;At4DmwTo`QlG1IQ@hw-QBJdI|mT^6S1dky^Q$~@vGtk z-uZ@pT1t>U_s4eT3^pmNao-nB_ovUFNUsn7&+m%A&kbvVxi#@#a(=OSnX~soN7|Tmm3_G3#(vz@ zCz;7P$vV>Ol}B38_JSS5Exx*<{z z<|8FYsZke9{JbZ5VQ*$es1HBU=tW=F0*DJ;x}z(-=m}Tq!}%iY$<41J7pl_dmJiZR z{RZ*(ncQ<%@`f1D8&J{x`u{`T;M4gBxI-`>pM0sgYr!XFXNZiwv3x8^yV5SjCj;(M9!PlCVn z{#dUXn+g96Y(V&zQulX32Rgx+J(X6Skz!aCAlY#0j1&cdj+L-(C$)b*eb!<) z7Z`TA?{)@{oeb`YA)4B-i26W8lP7kkP9R1!m60oo4wRt-#rOg0Wo88?b?Jpv(GTv4 zROp>8Rq*RZ{}BI?QcJ$qk6bH>xu5`aC>e=ielW@C9qqW%gFK|th&4-EAJdT@gUBIj z^vQpe!HE5xX9l5P<;Y<2%`xyF$ok?O<}?&BZjo|&b3U`Y(^=0O#=RP&E>3xgSi6{f zqMBTAK0QTiu9cbMM%KPhgrHm5#D3wQi0wbjKF(mjBtH)RH{pL3{@3Be91A_k;PgxI zw}pQ={Nv#+{KfxE4wwi3Z1~Gestq;mzu>>qu>bgH>)?MC{>1rDZ(#q-{~P?9nFnko z4|t%m`&+@kjykCq{IS73tof%j()YiMH1W5l_%FS}lXtGcpZOp5+X{cv9(U>a%9$_G zNG4w=kv~cQ#~1z**RtW?iQW%0Uz<9y_QN{N;cVh`XQY69AOlH5@|X=PMaq#vB%gJd zZivE03*l5?)C;LiU18W6J?M4%JItsVN6a@K_80V@}w?<$*KCIXCwIkP->bX+}*A!tI2z+8s__lT44FIhcL2_aWWT#qcr{ zd%LaiI=4N}5Mh73A@(=?TX~)aGk-hyNBqYBOAeS1e`WHV_S_QF3?$<30`{bX`L=>bR`kd35K zQ)lYfxI(?-N@t{#9{aa69=jP|zm(gFpHnj)>1xai$y|JGU@`un1b@(tYhx~ip3#WA z#DW!yT2X{OIU)FKV#dKwm?SIe2;p*dH{OV5B}ZxyDEG_ zPx?>&jQTQ@GhbFcBT|cwJ%T{0;o~8@2!Wx-0NE)oo(?CjJut ze};cMKWnG1yaeM$^8dc@$0s+XF)tH|4#>G2vK}n`L8$|!W~AS{SN7G&c}LRolXDHk z{u8i!AMQWvSgiXTwGJ|Tzk^E*{6pbS{oPyu{~Y+U7E_c-9bAYMuvaG&znezhl8baf z2XgsdmhpYj0hg6|*lcHPwhO(Z5@JRX`9U{yfZ5QjVsb~RC5jBOO6EMI7Jl9}llz}* z^cD`R>`DyoV&r&NGs|H;23sygIwSr0zkII-8GxO4=lLqhIc4rx^rS1&4L{SD_t%g6 zvXy%{j5*%N=@(2Yf449nhAw8!=<{qWT8_WAog?+M7>)UNj zCLc{QWXk$P_$R<$*e4*8|JB34qyCfp|7ZA1{%3>zhr*xvzcg|`x6V8kVE-NbCH`9* z{MWqmF#iT}xCVUxRj&Oq_Rkqm!I#;qZb=;@{7-Wh!F84TLm7WB_gVImM)6v5wvR3G zOMHOL3f9A)8Oyy^^iEyj9|3=F#2+1q;Qp{5<76F|GYe&B~sI3jC#RCV$*jMDMtW+<{!crI;MDkUD~# z^~+Ao+!rGGNG3DqotP`lMGvH}RF3~H!L}>$?SrZD=oc!f^)#wE$;tbfo%nZ=q zl^ZjtHQQv~DS^C4d|#%~=No-53jP^z7yc$4n6f?z_KAj{H1SV>{~5FWpMld)@V^ZI zpUwGyB>dCO{Bw9@pNx_JN&O#3{ePF<<2P!}JGhLHVfL*t`%&!F6`>ErddHc4#Qo$rBn#gF^J;&j z4F8aTBq6cn3)$ogx#W%PZ;aGPF7<=fZ9TeBMr~=?~ExJzs_nl)=9o z{@v-Pab8DIFMLb|`F$BRUKd^6yGrNntb%_TKA7q&hC-4gMmH=KhGII>2S zgKx>#AI23h>oZJ6pR8q_Z#lkyoa*`NFl>Jwwm%;BOX&67P^Z{aaO#5Cd^|p1dV416 z@cVXK#r93U-&FHGZ2yU7{!;&+gZ~9H|I23km;CRy{3ZYE;LrXD_)q(Ir@>!lexu*M z27hJ=qc524|2jHw4gS}S+OT1Bow7K~evh;syuTvWIue*CX3jj?9~}t99>RSLt;* zb02-^xd*d$#(bYGXBqkUnC(9V_Th-E14ptB97-RUnVHLJ#$0&KHqI(Y%A^m+?4NIv zUePB3iQ{Yf!N}9`>|u(}L;U%_a`lKcefKM~=79{ZR2|Gb&M)PD`|@5ulE6aP%?pEKhA*rv|i z-3I?1@IOk=_Y(ZCu;=T%ng1o&-$t&${|fxcEj~TV+*259lES~UUK5f`pEL#?2&GpR z1%KIdc^CfVcI@Y@QuO#g@}NuqZaz#fP~B!#_XUD+d+ zg-`?Jgd^UFLTV7!6K;25hOayRzk=R!8P8t^{|YmIW{JE+Dq-IP2E_CulJC_J@B8q# zec;^__UVWZekNY;e)J-#~;DY=1}{;gHAj{pJxVs^pYy? zB-~B>^Xge+Cg#f;kLW>oSpvekBeh=#|BjxY^!++y=D0feH^5)|zheJV|24zk!Ce16 z%)iK-|4aWT!RY^G_LC*ER+fkl$Yk~*5Joa9u4e9)*)&ld-l?t;>;`bFM=~E+Du_rOdd#_ zwA&hh1d)?>qZc2=+PEh@-Jxm~{gd(*XZnW`;>o~8mr621Z?6F(t z#X8RddVX`s|FW^Mxx|3Q#@eC7nh#jlccsshWab~oqkJuSpNYTZ{(1GX<|lqZbR*s@ z@_o?@(GQ9LlK(ZC^MB#bnoD{c{GI;`f64!ZKlAOy7J9|_OHmoTM>q$-#Q%HF9Q9` zkYWFox0zwtBzwT+JYH}3TM$QL*ayU{KY74=n>c4ED1^G&U4P8Evm0)*COK*|_wgfq zXA*nnG<)V6l^f^4JlUCZ@}6JJn)wCg@{@%g*5@4ODZItLnD_Bz7rAEHUy)5r?n4b2 ziOf$%fDp2dAFWu9*_w)02)>=5>6Tw)&w z=LEzjki*0w@|cWB%`YNf$0M;w5G3B)!b1umd^=xqyYude; zb?dHQnAVf^pd5TaxG^{7^}fHEKQjPZoZv5KYj_ zxSr@iZ=?^kd=f4WoeaW==;NOD=Jb(4^kUu5i!B)$d+rZ!5%s&n89sE1PzYzY?|4V|u75u+Z zPd~ApnWjSz`G4$&+<(ck7TBLL^QRwd@V^TG@6@pI1K{6VPwUCt-4~hcFZDC>K-u5w zf7}ZHZx4SfJ?3X;*h>r$`}c&m8~$JVK5`a-ocm(O_nGhR&)>ErKUE$0h*`S(}@w#64nZkdg+{y%t;YU)>{tdgfv->o9Yn*{%-*vB)S zS+8ZvYsLt!eYwbyeE!u#j2|91GVS60qL_4*I1>AUq|1~UWxKJb^l5Ml5)&4I`{LSudw_8~|BoMjJ? zoVzG)vMFQm^umU(W@?ijS3^JV|S{{C0se-Hlb0Sou%>>}aKe#~Uf zXGvz>^dbJHnwFUe_(vC+_-{W;zPQ>XRaPb)7_+*GdBXeT=eIah@{0QSZ#Pxtr2F_k z&StpJH4-dqwqgJHe(C?ZL=~_HC|Ae#<*b@+oT=sZFV0M14Jdn;u@@$N_+?{1)c7AR zvgUV%zTeMA?l1fJ8#Ar3*4W*N?qKj^H|?VcQ#`E#xDNYd%P}q?w)G?iW$po+p&}BYU1b2 znKe&P{(+0h_m|*1XCe>Oyr+(-+|v`t3B}gFQp?uLOimx-Krj6KcIL9)BHqhBG0u{t zf4lDs>B-3&Pzf=f^H}4$Q76dk2>Jh@u6ix!%S<^WzM+&ord_Dx`cpgfAg^@Q2S#mD z$@Z5y``U{==)rn)Q3U5VbYiYIkDs%r_F67_Q>LG6TL9xk@{<|#_GO-HzM-@0KAT~j z)3oZD`Q)y1*f;PYdJ}`qN3xz5$XTG$!%8;zS(1LFRvx z;*8lei5=@5M554#X81oa_AmX){|@{e;V*mC{}caCX8w-w-v$5g;J+XK=a72%UsU6N zsDr=s{+r={-?0CN&Q0*Y0{<;)buec_Z|h6{?@{)+NNg=7R}Z0n57LeCE$V6e2>LMr z`2Q$!b(u$|9@ykUttb2bthlZO&Nr;19{RiL{^uC%y_L0*kGPNTsJeyQSW8^Q%+zBB z|MlNRsUf4cAzz^j^HqJ^b1L(aHTj{Xes6g)YiU>DZx8bt``dbLiqPU*dxh4 zLDL#w5&VnD^9$imuYXV%?UdJx_{EGtb2*Hu1^*tyEZ`wyEq-jKh2GxPni&RayRGyA zzwV@a?nz^|A)P+8rq?hO{a($iZXVY?8UFL(Kbtu54mvP{>lp<9=lT8u*7#oJ+Rqt& z!^A%t{@G^s%=7&R{w5trfWP#HMH0|~1azQ74`SfY{)|s7=$o}-|M=UuChY$v{B7Yc z=g<6S{vI2}{)PV@`0s-M51fS{{I5M^|FWiV0pH3Qbz=@1^WBreSi5Jh>1ukym6PNQ zi_y#&_FzUTLw`7{ka?~;^-?Z-`V+#5neObFyiPtQ=hw@beKyz(d-@LgkbC=c&tvrh z^(S(oYpV9npNWy{*!T8>8WOVyKlzwSmws~ARJbov{qmR64DV@;)=0{lPN&PIB84L8Iv6wZQ-o%D( z5&Frk(ZN(>tm*&k_wGlJQtR3R+0Ww${|e6Q zS_Wr1vuPUq7sGxUYrRjS4>PD;7E-^y4ik4`P9U}&h5e^+zjAmk&Z#g&df6tCnGdHg zB>u}fXrdu!_9f5*g};+XJhekCBK+GP;(whU?KQa6^IDeh4`$w%vqxO>u)&V?ze42D^9uH?d?zzQTW1Jy-P(JIxjx`0z zIrb6j7h=OdaPL>cvxHdxiW+hE4tZUTTB>c}`!4ggFVI7+W#8T+^1-*%`tJrYBi~3L zMdsWNp&#t)C4ZY&Nx#3k5*_250Q`G(os#6B`39rkJei@!++qz;Hj z2k5gV%VQjR5N*r{$X>9POY#{#;{Tb?VE(Im{uTJ2!`IwX`|^T$UnvjqFCZ6?_%HmU z+1teD&Nyx%ui2tPm+yf8DfnN({;$CQ3jC#}Xe9rm2BiL%xd7pRFx7@RhgNi8jT+BB z>=o{PwSQoDW@ggy@qyI%&gk0%>|WSQ&tLq$ocm+Nd0!sH3eNY;Y*#hWo0)&ugAM&e zebmUFsJGyqO1$Zg4(vAe*lj$082;7d(c_4z@6kWs&V4o=Wmle7y>PPCUJmZ|ck27C3&SZ|ye3dh~nd!sNAAWuf*Y_fR zzZ%=0!}rJm{`!i#n#ma%ZqD%M+{)?<_-DeOI^SUb-}!gcf2Mj+bRiaDf8h<84>9qV z&k}iZ>MdAt59ZgC5C6yvaf_1ATM_TRs$bIB|O?#~<4Y;$3GlfKeIHL7|kb@N-)cAMaT246pu zx_&A7d@233H{m_f*n1=AVQhJ%89#SaC0B5U`^O&Ip|UfxzPX0I@7*JxcfxtRA$j!r znBlvfjSl3X3vz};7ivKAz0I=_tz_~?^P2I&ZFPdmHEv_jCuXq$sZ`6hIs~S>F>Kte}dfqIm7m!zBq|o;uGfj zI4iEv3g7L+Yh)7lv*?ed^L3h8Qh7`0s{Yxx}%t%4xF$qaTqz^=Ih`I2VOLv&t zYr}_fX7hq3R|o4F912$bTTQZ+ufM{H3mu^*`zV?uP$ad_XkC??ia{nswFxC%)*zdYo=5za_WBb{odrvJ(Yf|aO zY3v(*gZtx5t-PG;pG}=N7umq<_8aKL%jo8l_`N>p!fgE7T(0$H^qBMguA)1AT*-;U zVagl_^W339vSyubmNXty&62`nGBtA&l7h(BrvJZCl1TqY`asNqm&9;}a4Y=HXLiH? z$u{=klpTS8lfhs1e+^vAY!v$`#Qs0KUr0_^NN*^P{i?3U{NI{)w!mNPU-)0={WZbA z3I8uX<+RE^Xr*NqAo$aJ=zyF7@6TRGJ`aeYd#M9ncw~^?n#4W}bY(Sb&<9-|I3LFu zJ7*4TyFce|HLDe$hM*sI^u!ggfxenPE6ndAlB(^d`w$zrny+3gd>uc=Q`U19p zC+;K@E8@tf9;LthK6BfB)cRLmG4_CN+PQ>BIm2Z++?SvO1G!Gl>m2kndF1QZejGWc z1M8Gw#^+HZ&R&&-ib zVz0zP`X>>rn>8>e>7cE4$MN}U1$tdphHn0f{Y0F}x~j1R`xf>x1C-4ii0mWbEa4L= z)N1+k>`Pgz>%-6cbKY1#-SVOE?+gC{>f>h%VR}(* zyOm0ADYNu1;tyuflb%IA{|0_(5%L!7R~oTNX1WfHdKtT4Odn`4xx+o`ReOAY0M`*` z@Lzg90{-FfXOC%-%r{AoCk5ebC`9I1Ouiru?x~3AKu7;i>|aEBfiVXEnd5G;SL-@C z8)x^nT{H7;NxUo`m3=VG3JT=?IIzpMe>R12)E+3Uesx(0s>=6$UY&UG(p zXJ3Squ6ci;v7h&-wH@n$F3fQU!#+Si`@)ad{R-H>h24LLEv2*HZ3WDC69cA@+pXhk z`g@a>!h8-(^l_kh0YQZ;W)FxMQ&Y)3SEulQ`9o1M|Su6(wV zHS4?S`1fLLzL5O7D{HB8jzk81kv#0VGv`W{paYuOpfY2ow_j^tI1j>K^nrgB_TNX3 zoXNi6sKI<+Gaq29cmCl`{MHE8Y^Tr%l|62$_Bdi-K~(d+?`y_#}r2i6wxrKv$;_=YwUgBMj?D1M`CY-> zZ~9L!T!sB&>ZmsMKId@HrepJ8!2Jp8`9CoiwU%|#jpP7v7GD5&pE)(~GEJ zt{|7Q(Vu+k4Y&ut1&J>G7;*kV{|Aq<8 z>GC zYWOigr~dwwQvWK$zbBH{=zq^Jq$!oYVlt~wbP*C#H~ttXXx`kx2e%Z!|{s)$veJP z`}XS;EBo0I=4;?oDbkJaeF0`A`o_`e4KC-7l&vHiEPeP$xNy5o03dEI1udL}-b z+3wYe@J}Xg3Hvm&$k&Ommh*>%btn>I$T{{$h+ov&KKkyu4#NK;@6~i3s+@@^ z`J~DI8_o95XOzV_!rv11a?UTGcM%puFMXUk(`Tq!lzLmuI4kd&9>Hiwu{GAe7Sq$| zt8zyyqPCN|aXM^YV~zGPW@GolCw$o0$c*D1ts)(?&|w{??Q3|pzCj?ia2&*$sf8g=JnNBXPf8LT~((Tl9$tm{(Z zKo|0XZrtY{2LCl}todG(b0sVJKYR9=?f7t3Kcmn4?0pM9v-dr6IL+s<3_|yRP!k-= zS>wIV9_k?>2Ye;5U$OQnm>ZRoz!~5pR;7F zV(>RH@E4mGf1ZdW8IMo?7z_U>xJSY~0^vNuV-e)9VMq{h&(|#eJO=&3|2@{peOL?I zTX`A&$KZbr{@%agUx@ub%->C$&cPG@2k>EXCbsE3=R%*MO)=enwMK@q%Pu5alkYBUSh#-^|b-HRFF5%l_YDZ5A;_Cn{; zUtNV=eLya-(AfJX=bu)se1p938u4Kp`M`F1Y}M$?1Y(aDwU|FOeJ|EvipkpwkQ92g zU6C?g4`1_rApLkbM_4hgxmzSpB5(U1_ROm-OX~`U9@u*&=Kyr4 z_b>|~+UkXP7-F*jR%5@u&)OUCziQ@R4}Z`9%HL-#pJS0H z{P%GV;x04)%kaO4|8Jv5e-r-Ku>U3)u&>Ovm3Y@eOtXVOpR2Pc7U6SG`OMtqZ|$Sk zHI97@UV6rhm+3tp=bp`iX`S+aYpz;bu^ONGvKl?qpU=KahyT~i1@z&3lQ*&1?d0^S z_<$Ppp+7$082mZ=j0$Y z)wP4=jFaQ4y7zmmFZYN4pPBPXBLDQ|_ok53yhmQ}iF*1k;sahY+y6q=0cIHdHzo4< z+B=-!AB69ho~`8lrru4JRU-DEfc?|Y{Ts6$%%+?8$HJepgsBTm>=TUnLS+#P|9IF( z!#o(_GZyY~?&>{Pa@?Er{$>BCoNIj^Zs+L*?58e21pg4udPt(5mB+k6G4H*|c>hJ= zo#_L~=R@;ZjbD8Qf6jo&K0*wU{a-)R-;=XYWiRRl?C30cuPtXx@_A6t-BFWw2C~lM zjUOmN57?{f(S_e*J)@@$_4r%lc(2mO`9`hX@d@h^r`7he|5TIf=@Y$O-|g8K4sZ@i zE%)p&wo{58yvY4^(B&I?G0QawdmqnQn|!u$4LQkud_=p7KOp_OG_G?XJv>DnoyBKG zF!%qbFR5FPu>QY;-sfM*F(xYM!@EphK<{>vn$73SOiK#j3=%#cI9NV=Hkg>lXM66= zr|x2pCuhRAU8|xO*PmL!p8IIYyvN^^)6qYWcg!cIZlGrQfO_sl`e;w813&Q^UvL(H zOAGnkXT0VTUi%Glng!_T@9AUCg1>x5xF<8fTj0NwzRyAaj?bu{ ze*ymIjNWeMcUP!!Y+0lGh8`0>V0#v|UAnO!Vao%#mMCgl8*Q`nZSKh-Y&ZapUsAt) zqmEJ&MsvQ*_OnM>$LBNq_D9({k#xoa$5O*h01>6-7v>kR)4@k`NA( zty0z`kxGgb{qE1#jL$jW&+qfcbsNjfd*1KY94x*?;f>TQHQM$JI$K9 z_iVqs-S&dr;D4iCxUYE6ftxYdnS5D{@wDGRo?TuA|6kyL8-0}EQ|vR7z+I8M|68lz z@7D4@8p^#~2mc%SWnI`$`|cY|7q3r>9!t8{=Tu|L_=__zT1`6S^+&+J1^i!uf8gns zNNl-!Is7N#A3dXH@dTSS0{)}@{z-J-P@l;T&7GxSm`rMV=KmM|Rb%|qm&)fa#qG<& zztW6M{C^!v59AMfF!*@IgYK`$*Ddc1q~I5;qt-4#ioqt#=R5$rqp<%xJ_9qK_!y2` z;cut5-DO8o{x|+|0hjduq!dXky?+%>({{CvJznE5oB5MF|E)7{9QN7FWZA4Q=r>%) z+&JEy#CEA>G?k7y$x|<6GglW^W%(R_Z-$!O4l&$w@;6)g5`F2!aqvF||6HDX7q#6H z@E;HV$*~Xke4;uZZ;Id9mR@M-8Bd9-y)`syf2u@c*q!I2E&Hm+Hh#;#Fb4?VFuj}k z$z%DH2idF9eC}t|3ExtydsnL1e?!D#OyU01tVG}=tzZunK!=9q@@UN0+kT=v^ zd$1$D=w|bJM~?Hek9dt4@*H1AuT`z8-)Dh1c&Hd|a$LJzJ*!8Q^jDm}RroAs18%u` z2mIfR@&8+^yV)#zc^^>=gh(X&cXMX*>&g*U_=joh=`X#_oID{*pFXHW5eeR1ZG!4T*KL4@A8- z6iloe^tIpjsd!~kR4>iW?#464Tkpa?FT2!HUa*JUlbJJTbH_8N>U^35{$VeX-8)sn zE~K02l`edhgM6AV^s;_}*SN=@FPQ;b6VL5=HL8Eo?^D#LhnovG5&rwh&(ZC<_1rEx z?3wR|{}}PgUHsNi`=3Dnjg0-oqMw!L@0W_P54HX)x5s?F}EF&E6Nc4!nproPFslo!ye}~ox>jA z_3&R0|6=MP!TvXcd0X!zoPjoKz`_3O|DgW|&p%{W#0K$x*!OzbT|C0xhk5O}@k3tV z56*}}kMQwNe)Ft5iR)VNTkcR#9l^$4FF)GSK7!S3-T^+tGkmQVoc~Xqo44VwZ{zT6 z_-FV1Q{DTpyS5wr`^x*z;pa`k?V0WG8J^t$deaV+jkTTgH2zO{@lf!YJFA!UO+<6# zg3qfbHm*$%C5z_@CUV^Mbu?#<9cI~+-1#baeeH4fZW0rAlJ5!J;;;iS>=?FZd78Z) zVyZ{CG)XM4ew$eG204mjQE{C{W92dbu=n+Ee4+=c*ToT76~_&`t62a z!k;sx`MFG+A2{Ky@`rhk_Tj<)$QPx=fJx%PB)*|JvTx~+u4(p{yw*Sdv;(KdZ}85{ z*#zv@bl&eAtW!T-XP&KC2EY8`IrYKca+f2StN0Cf%zQjRwo{tD(A6=pYsZgyL%?j$luJ4c9yn#-)Zs_D*M~uFjXZ=ha30v3}yZLHZ!=w+eF*apc0xyW%<1*^n-J z%p3733yNUmuNKSsI3g zVOk59H`CUKJ0JS`1OA)MoDSTbz}*P-yHLZ~m@QpyP#@VLv!|28%=^+lM{(E}g?$P= z78$78=ZfR({C&spb1%tx z&ZVKutj|La{o=v(yny)hpm>C7VMU_*54k7j@Va7vtaIew<0P7-$3 zM}zypKcY9E#h)7#bLfWrUXU-gQ%`Igac#sm>qgfX#UJ@T3cGDq&nib!@eXkG)M3I5 z=?yEIt2eZuvjR^#^oY0e{A$y4?cti?`zpX+3|v3i83-|JS@?(9QoYp*`|9Vz`!7}3 zXRKvTK}9yXv@@66`O#05`y;mPov7DskFcfF;s2`pzX1NY(}BD8%0~E)Ww(aX{rtZR z1Nbp!rrWz(DZ?IPysj{_^K3(N`eOXU8K@NFAL@M@)v!VhZ{y(#YH&fy^KnAXH-(fW z0sF$RZ{&O0xcArLH^_N>at++u;4ihsUoyk|uEYGGo$9y$!s7~DuE6C9cWq@9`Yh_D znOXLmxsR8^{SWxdn+FIgA|ga0o0<2*n9vptB}*;ccoQVYU8J3AA0%cVJQ zMdb6|R@0nn2E;hA+J|ELGivj@<>%#S7WIr{x$OnAVg_pmIb=1MJ+DViX8noBSPWMwnFJrW z2L4%nI-n8!Td)cE`^)*Fxtn>;VOMku7@A4jx<0O2CBDD?jAl6=uWw&`<?uz2-Wc8imo{`;T_y5EOjP_ZF<5WG(md$epCPjJ8m6}?nfO_X5 z|2{|V>TmHZ*P{LI#qZ4H?%H3yDurziK3-M$yX)O6X5Ia8|3jU(BK+O^!{xHDFXL+& zSclqQl5>#X*V6v2zVGP-`_6ipI_o{?7~lKj^#gaUJzH$fM29c==KqRkI1F33-ZOn4 z{`1ZAc_Vu4Tw$NDhR<*AYm9#@ac>*=w}E|_3vcJ*-xkOh?qmOtMO~wu^kAAkyfTUQ zi|lP%lN*mG)Vde&>!-=*%oOK5EZ4s{8nO47x*YC%v%Kb?y^Vu=TeNO=KYX~x^w0gy z$$R|4i}IuIxFfaV-pnPh7R0BXB2F6~=SxQ4)fkVWvom6*+}g|hkA^U?)9>c4e6tbi ztRKWNXxJ@#XV~9fN^JDBe;<$MW1kEEVE=8;vi(W)Pm;SIV!I%E^e+VbGkv?D`!fy&T_5gy zz&?$nkWBo~U9t=9QZbm9jBWjf747IN^9a_2JatF>$_{*%u=na3{f`~pk?Z6#K9L9e zF!s@xE(^V!Yn+8yY{IMdR26_SsCG=F9S3jvS=^|8%_1D7mtk>7Qh% z4|GVZeBf?&=1y}dKa6Ke)>vCSQDsxu3#R^cp|bw6+VHPW$2ap#T8ru1;%&6m2i{Uo zbcVCgj9zc(oY>Xv6>7Vs<3FFflni(CAQlvL-z&$qK3A!(G45-7g}5t7 zM{_qjkWQov>E^twmrGl12Kl@E@D=23m`(HS=E>{iv)|Bu>Jw(^rDVWA%ztS`_qT$5 zEnGJ95PEH7D?<3wZYwM;S4m<&lh;eW#(jU@z5d6)2Dc9I* z)W&D7qOYes9a6>)m;CVG4gU$|=syMjaq-ztum3G7kF=y2-iU;MuLxGqY4Jaz*sVe=X&sEf%vq>)E{w z|1e{^z2u1*ag+`zY+iZC=b`%Ygf=$KhZt>j_%JRMM<_` zJNr4ChIyg2_}>44^P(~Uiw2J~JH-)ATH+T=u^GiAkyMc^N1j+dji zTB`*N#1-x;E@1s`S&|lb)nze(_c~GKhs@i5+rr^yKfMub~m588~$Q9T!^)JsLSMMR>6NU zSpffu@P8@#$DaZJs{bec7j|djKjn>&?7%u1&CHcoyq8U#&0c^Z{Lib`xW6mhZ#{5_ z-;)E|L`OUc|5fh(7qBl#mzK6~xFUV>q}-+?Y`j}P#OSZ@~H+J+w65KXR_13#x2y=aEWf61#B zsUQxh0)Kt+=j%C>c3B*4$Oc?Z2Q-0y6aGL$K3$mmV|Qqil8IK=rZ@vd?54Jl*sihA z4@K1~H|G)7MyXDMDsG26_^*ZkyL{1r z{~Y+MwZ1!@JF00dTZP#gU#kbI0Tv5((F`H@ zkJwv2#($yN2w#0&$7|P&?RSH`4PercG=hIU_}k-prUd*;`Ml-eZ$?9{P#+F;nFivU zf$$%k$Tp{{`M$OJ3zeK_Gl=GeXL3ONKQ21(=-u$o+`Sk2ySm=mHfnO&+_HT^F0oOM zJIh1a{?qCNDeiX){N<1TNQZxototAOG%|a3UQ7<$E%`lpsTV&_jqyK{67m}}fGv8hB!VUb^?(~-&X5l^h&^E$}&WJ#F4y{|2xxJndo;;^}M=x9}n@1K5}=% z?v%~^yHq~KYivWWXma$V8b!UR>6Y4Nwfr8Xch4iwQQ7{5C+OH_>hFJ32OdD@Zis!^ z+s%@yR!qN8o6P$^VsF^1Ttyu+*U&37cO<-FGjFWe&yS_;GYctO7NEl z_)+Z|{$b`qjntdOQg*|dU%ob}y1a0@e6hV_^YW|5ekkU7L=Jek&-1#^@}c`b8vga+ zpW^wXsdcBAQ!rl6p4~x(8pzPSe!xK698|(ly4uLG|A1T)U#p z6W%ey;l0@RYxB!|_xO3ay2a*9=dtt94qJ2a`n`kuy97@zr4_%gwYlcCaDYq5AD@>$ z+$jdkkyyIypj@xG?+e_5$KFz(*~6bWr7vffb3RyZ{{-CMB@@UYab`}nKODYhVL$SN ze*b@~1E`bWZfs~W%g+y&4|!OvF7yvwCr-{#SFesoS4utKPTxPoqenZDY>5U>4v+(` z<_z@LcX5|9{kxoi-DJCS@_+K-0H}Yg$EUicg1mqkAxOHNZmCJ^-BPnVS+#-QUNhq7 z))MpAVC&11oNP|W`zDL;)k_No`=2DH3OT>FiC>@W#n-H=MsBX=xpH`5W%Ush)a!B= zzUtljk)LAoW~&3K_5bxcb1g$J&l_^l&Ex>&jKwm??dfXXI9VMIhkHgfQVT|@B+Rf3 zIo;s{{Pp(SJ@{nwL1}#o6W)XW2G9Rpe&Cz%UjY9P;a>v&HQ{d-@;lA5@NW(O zHt|k_TGdM7sH}qj0sg{zF~kq>zbY|($=IyB6PnrY&#`3+@2-Qr%FaL?wX+y?)h@IMKEJ>dND(FIB*jVhpEK}>N-(eP{?;Lt zNo_t-1DMw)=EeQ1Iw`8=W1h&1c2VxiBeu9GCt^0Od2>xWU(I&4G7Bf{6g5xF48raI zD&wh{3o*okHKG^;^TSFK!`I+(CdTf{kPT3-~p!@44l0UkF|26RMmW6+Jy`ncbBbm5g!9SdZ z@Nb(Z?2hL*g&96!mQ$NwR^cP9fd4+vzZ|YkZTMdee|6tjQJmVBgGl@82g|%-|@5*S9g+VE$bf^M<-K zZ5|D~b1+WtCH_ZF`nWnf&`$ki7LG(gT$-Q63}^ZOrS*}PkblYL40KP--S{Bfd+M>8 z?du@6rMX)C%kU5Nj4$Dz*L^PlcR8RI;=dNDVmY(iKCR@gSMpl5;akleHw&IXdV8xG)a;TcrtE2PSErpS5aNhCjUAN(JV zbyw#7e?P7Rhkn4DTa1DKNWHu*+{w~($FuOiq%LVsgI!kn&xiS~VSag->lA9eY5e*M zalR+xP>B12WXAvD?pLXr><$-mmdeuoO|$UtD7VuI{ypH|!~Op^{soWrpeuXEvaxNi zSP$HD$2NEW9oT=s_ge}7z?**u{s-aD4_RGD4$O?(k^%oPTd`%<{STi#1^$P{{fpxK zar8k>IbZ!7#q1U8I|BY6!+#t6Ps``cR+lRc`?uwLHj^Xq8Hg$@6T@AOLj9*rlLfdl zEBFbA6sUmH)|osf5d(e`+=u5#Oa(99xaX-vR*mdi4_}>Ko z(eOV2fBT8d3AntcFx*SnUmSe7RIyyxtBMPEBt6dCo!nFd&h<$B#4bL^u8Q?Y*m0*O zysxIuQjQ-~-<+ZjdVE?W&fec6@qDGQ1E{XJKV3|arjIr`vHIpc>YCTlRb%z4jM4iy zpT0T_{~6KR2N&~S+4=>|^z$`L3_e~KmvKv+>t4LJ8XQwdnB|DypF8Ay%(PMK8~Rgu z_=mheX3i(@`9u5{?tZA}{$q9_XUTr(BJE<|Z|AB`;=Zo%H*5Z-o_cG$i#2;Bmb{vY z{hg`3`3Ak6fnNTtm-$^E!hX5<^Ih_$=lKTwS7qUE*1G#Y?no2(2mOC_oCgZ_f9%zn z_kY*iu=gqSfPEPCZ=W64GQ?>1xAxtxwz)wqIgfb{ufTtV+}?b+ze-*qFUtd8RLfl| zM^Zeo|Jcv&|4=q!zu4^)K3<0W$N;*sgqYR-ife1ho9V?+;|a5brtg5T9zP#;Y@LF?IrHxS@%ngbElT04mX-%hF<&pl^_84~bokl7yRRy#OTwg7OPXi&l={gq?|GO$ zwOg|AKPm^X0RGEj{Kr0DQA}Kgy-7|?j&NgVr+7X4m}iIFQJB#f>VE3Gaz6Q21t}-D zlLr5g+bzrEG)zx|sVV*C&7?1i(~ zTU^kaT?xBq`rzNKqGy*x*@mx){r}^iEc&1OpSNJ}ZEL~5aTflW_rKq7t@%84>EF_7 z{5#qHQ}8!;rF}`gRJh(7)pJh04F6O5vM$T%_4f=8sD(Z*|Iat?wwB;PcKg~rS?|98 zF8_O3JrL*Yz9mt;n;tYPVzQY3DzzN@;*wJM^1y$;I&Yd>&Esl@E+)md4= zPE3dYYw+(a-Uxc(H}-!8{Bx?^7ZJlJt6iv(RK%w(f*T_bcyGgWn5*rYi&)2u#0S2u zVz()d?vm<0TV?+iV#5aVLwa|s)Td{AdfpkZ>H+`A{Z}`{9ba}+x;ZvK$>X&Yw>}I1 z$LP+%^uT;Ppbb9z5_yR?qSZH#5yy6S&&uH96-`Vq7h=fo@&$4NLw?Vk)f#5Do^4nm zmh}4Run)PtkmtXGeaQP|;=lP+N%@KbFs>*5?FfH!#~SHLZQC3E=8M17M@~Ew|IM`m z_Cfle%Ej2 z;@lTd#T0jHa=iSS8lxMY&ceP+3_F5!C?>S9Ndm!aQQI{`_H0Q*DfI| z#g0eug5>#c&x1b`o_jsIVzoKA6~y0HtL0y<{@*U~XpLoJsU7ks`c|gT=O^~mM}35T zeVSkR2;S5`oqDFNSH&M<{aRsHf$~ytVo|#5#u#hFKnepsHxr_^A^k@?RR5JqJR6MeD7EM-hlrY zXE^NT8RPGB?g)xQL`1)!Ue7;53(RKac)jP5JU^nwM`oiC?!kj%}u3z2V zZ}nXtd&&Fq!~4b3WY%+P6hU6Kk8zggHkU8A68>MnKRM2Ato-?Ey1ym-Tfx6A{Ov>7 zZ%#O;UmR^?7)YkS5HuR3uF|5kt$kpng_beUNdb?hnIDQ9`E1ix*wvv-!Snw|ep z-P{6v%5U$d`|tH$dg}RnoG!A9^|#UbR$hZYp2PJM?9?0Ou7q7K=c8Fgt}-jG6nx5) z)EN8q`9saGJp99a*1+uu_=j3wrtRN+D%kzZ+}{=aAMIQuo)=v2COxO-&i4O$fcUSk ze9!gHg}Dm(`6~Z`f8p=@It$_M-^jPI|%>p;GZT=uH!tZ z;r|c*?PB~dpQ}qZmXdQmNXMlmmOr@(C&SF%HNoHBt9N6w8qUjVxQlThZifHIxVjr* zjwjtX%p}{x@B2WHyq@7HhyC9v_`d@G?eGuxF|WC*L*Z}E+Ibw{Br_lT<6GHn9A;)T zc=EQmk9OHhVOEYhQM=LbU%(%j0f&cuj+fOm*Wt46*LP%>nHpfVw~ES@mVmwfw+rR< zv?Z$-R3V|xlS8tUD6rywcj-^5=EVIsEa4tURsmz!!OzC}O1FzV3Ji{9r8&cGbcZ5sS{!2hT`TB_W39X(Q+ z_!lnp@B4oR|83dTa8h%zf3J#f@+Weq7qPSMBl(*b@$>J--MmxmU(H;Gt+3w=^EvR( z2LI{&`=8u@J;U(tzI`$K|B1eh0`8MJKYD=w=xol)L%-q%RJSvuO=9nbTJmVGi}M~( z`)Q+Rev){?tc1G)2Rqh4vpp*ZIXlk({$tn{ao=dV^$WSxzr%l>dPz-lBm!q7odn#&-4DF( z%5e@b=Jx2@3D^gzQnkFiUs;&zSL>=Su>xOhMG-w`1=M^JcwIfzq41KH4RH5+s)zKp z^ zcY*z@&OkTMcN`u2RE+RvKv9X$AX2$+6CC84*b)8>brgUO8w)#nw`>b zbgl+?uLI!SH^x1qe8>UyN^EG-htFZxsIw6GCztn3at7>Ik~w%OJL!On?rE579%fq?lGjb5 z)8+VoP9|y2L527%jCJnD$}eBRe%IWRu+I~7d)DXA>3QnMnpca?Pg65q<310-NgfdQ z?<9XTIvRayhPXseO7ro0Xg0*Y-#^~psTb6ImydVx0Y~Aiya<20>Gp-$trbbw&m8W( zxW9hj_GQlXxYEZvGgmr3yF0H(bykrSxa-SWTAbwPYwL+E*in2uKz;8yJ6Nli>e~+{yQJ@(=JY zk(fI_%nNPuANbp)e!{M@qvoWh1sn9K?|;iovoOQ?Q~2-4XPxge{-L+QymI{+Q~SN9 zPCHMXrw{zipWOPVdcix(E*>EX>x`y zkUgG3a=KX9D;;Wj^bD?jd)%5#DHs1e*xgYgJ-Kb&cH(-O($&US8o&xZefpJrvo<9Nn_>7N5a1j{KFn9 zb)yURgTp`6dGfCc*ax0Z1+~7=$C<8gBOUJL*u#K3Zr!QS$5WK#NetZir~KhH?(|LZ zeSfs`0CkGq{FDLsFjw$r?;G?_bbq6l{8e}LCwq5?^mMoFw|`_Lyhe$aAA}u#V(q0- z`$gHOy4io`Q?wdga3Q*KLY>KiRotwC5}}wE56_IX!W|niDfS(dF}V`y$Z|s zm1dtp92IsS{FP7gbX*$_cf7%q_r`OS^41<1^XNt|8XoI|v@c$Wd7|S+yy*T-6>~qs z=bA?ctWZ1NDjqzJZ*dhnE|44ipjzV z*?~9cfRFe9FUk!+pns{p_^pvRzcR7Ip?JQLJipMpAFw~Ru>$M^cQs(o zZsiR^r~aDD{)(J(d7b$9gV_51a_Q!fT8MNY|L@WMoJsJn0{<`rskQoVOMm`P{8QlnCHz;&Z-rSc zddJ`uX4Y2e@UhyjxbL@t^x+n||Jf}3)8IcLj*Au+!&_KxrvGQTLHj9)$lB@Slg@@T4;_7PomAeg8H5-*e{PhW|UB?M`vv zJK~}d@NYzSHiUg8awXT7$?Xkwo-nH=%x~D1Zq6?JlhmKXy${$=FiR@zq?uPgC!fAB zpT0YPelYuC21!zoJ~95`@3()`2mayi+ckTp8+{x6#6I{zVSh!$4;UdQHy-}~h~w3~ zwP(TqXZW9oe@XaP{$KdFbpP$pcK;t<$A9Xx0sb$*|0w)t!T(vlPw3A&1OM^v|C8{a z3je@!t;_D?(`@}6{tL+&T%bSrgjebR$dy>V4#)E|J!OB{rJfV^$t;&c8x8yMe5}lz zY9{_q!GART7smL9SpiGW-VE~*e#dw+(w~EW8Sr1Mp81-dmKVf=qn!bLML#X_XESNP z$?Y@bAw@`va~J9sxZDS8;d3+)%QjN)X{pxNRG&yIbN%Q$Jdshu@sjIY&%YTL&n3C} z;s`$7Gw@#<;~!$Z-P=dN-!8;i@5lI8D*G|~zj8KqIyX;d;a>;#A=V2$Jel)Y1OG4M zP?*~iX0)Wm{ri7(PLJ__z@EnmeA5Ywi^D$PZw~rXW#n}F(xbP^Pxld<_JV(JUkAkJ z;rX26yz_lx-p`N>Ie{u&5*?n$3u{yhPr!b~?;neMC|>I@h#eh|ul^Jr6=orS7oGbm z)V!*Q^Bc?ew@~-N0Vo`5zmeKcefhBxbmNa6 zE6pjGW!~#7Gp=4T=W>p-xs#nZte)_P`Ah}e`_gnk$oJK7Ch&wy*x%f&u3WEKY5B|I zQppLO-%Y=dVNV8#S)U+J`P}2>gC^m$JjVwaOQ-669JB|wVFSLxS7KE0%G%GJo2~rC zr~Ga_L$TkXoq4h(^nK~o)3XwyNR89;?|Ca{~g}5 zS*7Lw?u?Fv|0ejuZ zSK+@7{*&P!c*Xsl_b?0ZW%%3WU84;A*TR26^zc6y`CZw>tmg2J{Tcqn*@RR4`^|E0 zEff9c9X31h4)}*%X!?B~Sqj%T;r}@NSH|_8#t5n?w7L|N*?rmPx`(o@P9_# z;lJUJg9-n%7kBGb{v8jeFh3}Tk6fCsl*-PhtMONXf2ilyhQI!gb@q#Y7Ir4rhrbzM zJ3GPV8ki)DeYZOUbG^66^t-l+KHo7y?dlEsYC1n8%x!)v%5iEx{NIEBZuqZ&|6cLm zmgu!HlcG@PxfLIEbFI+##vk699sc>?FHcd@tlO1E;bs2Ldj4pEP%kJ&QnSuMUSEUc zf_)_J>#t67lNhTz%tP%5H)&buWA7B-_l3pGCdV7ucI3V2)~9>(#WKX&!|BBNeExa3 z=@aS9!SKJ+v-&Gv?k)Q4{b)%wTpsxEI;#G!=2Ns^=;aCcH_N*JY3_W6n#{Yfk392v zKKH%w*Vp&(GP!?${$ZH=udY~hj{BdJT|6q6TPCsIT+d@w#z!}=85T{)v(w{|h$?(b zXJ2xMu1YNY+i80u7DRP=xDywT!@sTjAL>W{@;k=T0U^)-fqVZ12|4$0_s5VYoaJ%8 zKJVXNhQB&to~L}K=ivWIRCs&#M1CBdA3K&%J1GVK(C3#7|FZBeYxYYT{L71ND&mD# z5#v=+f5i8h9rzgPUtcIXX6pcxwx0hr=0@iizr8NTm|z~w47z=w__;p&HN$)S+Ie`J zFS8N;o9WiDtM?=R(KLM!_@em>!avyW+_*qF;h)p%XUD1gBhC?TJMtUuTVZFwUeNW0 z2%o<|0g|0$BYDMeT}e;)_kn*8(hctYa9jGj`)X!uI^sT7R>wUKQ@VY)nWPP7y89pM zH#R?g`T?@Ra}P5t^q@aD!n1nW_s(Wx<=puoyN=?2{T6s%!3LQ9yi5+DT2uGm9LkMv zu=i#goR_N_X?=s9%vnDah5uIf|CKEL|DH3jGd=^8KO7AIqVn+XMdhpHg1dbN%hO@M zK+Lw;`#8^@TrxLxq5F@g(SsgLDQGran341#Y+r?Is8jWZ{|@ir+VJf|+mLGe7)u!T*Bie?~3g6!~6l?MHG! zZ}Ru#eB7g4_@3j=$SLQphj{iH_;({W(gj^(3HhM3H9gFVs;A#)AG{yuqXv%Ei=Nj8 ze#s$uY24i{c@ug6m4p7?3;*{#t9g96xni3U@c)tzSQh@Z;jafU?dt!*fBWf%&OqS3 z>|;aTcg7bx2TkEW2=-xSTVwh@zxi;#vv!7I)Vf7llayqV&pl-z!*T2A?x&(UFD1vW3; z=dNJxMBu<=?%J4op{?A^^>k@RK5Wm2wO;~NCMx=t=q zFXf&^@Zaffy&-=!+s^KzI9KUv$VplFmxO;3{Pk;pQJnrSpy%-_d8wRs2^{ghza`(n z?_+XWe|jEsO!l&eoAl{*mG29ChkMWi@;_Jg;N$m{6TBv#6E-gU$8^wB(PO=yiO&6F z1zg{fuUR49cOO65D?eqoVorUFx%1*>Ul2>}Vb|W^C*2_i*be_xF+iRF!GBp=nE%;| z4X6YEP`@Z4k6chr^(J@k4)3WT{KrHS3g!_{|I9~Ln^|vmb(6X9KSTcyfd6B10{>F; z!GI{9OI=2P)LXk?{}uU3uIMPcnWir|?4f$wnSCc3mF)w3;&bqQ6W#;kJO9AYb#&tS{e)cXx7A zIh=rUYMW}qt;>m(!~~`5;z8E;S)1^wBmE2=_)}pHXfGV_8%Y-5=*D!`Gt9{fzK6mfLvP7FtZ^U{w3sG3z7nA z`C&&&Hai9WP|w`vv;Jm=NilWb1N_L(%(S(8@+G~<`)`!f>n6VMNhkNA^Kl#gj>kEB z6zmt!Is5RjzLk6UOpnf|{Ow(2W3(}w`P!?}c{#MMtORql${`b-UE!BPJ;yE;h|1Pt6db$5= z;6G9?pxyAtF3I_IHjiEZ_**ZC4Fd;kI5`OWKkQ&Cr6<&^cy^)FEl=6ck(P*Gqhv+- zp@FasIxIJQchIvF^$$D}OZam*2i+(AQ;uM`yEj1%<~jV6&GJAK`I&eplMBe*7ZLlH zk^d{K_telPZ@ z4eZTSt=qp~%LZ9t7b8aV^$9^KJt*nye!nx5z97Vy8Xmy2vDr-Cy$R2?a< z?U$hgf(=NH@Bip+rD0#%GY|QOB3?6>+=F>Fi^86sBDg0-^<7^U!?b{ZPdv{Xy}!Qh z{*CHEJ=v&oVv6@+j}KY;u={?{T{q+FBX!qr3#%1>$F^@W66XZ};bqUHfSh9mIso^e zOq#ez4{zQl!~^Sa)zjk7zg&;(vP)s^o7rD!t=WKv@GtKyoR^20D_$Fbw_l0>J014p z+5f@tpQHxxPxrqQ{e9FKm?IYar`HU9FCF1OM~~FMqGR*#a|YfdZ@~UExxhy(B~DC% ze>r)8FyBpY;61P4lyo+;thF=Lp3ZqiooSMD_c*zaZVWNN;y4z#=R`;MZH>Es$UfL2 z@w~t8lZLXF_WSnu*_;Y|f|N3FPa&!JzG^|q$&H3U7&+G*MX(*rS za1?A%*bx-=$miNu!2Aln_Lx<$DhK~Y-cP%j|8oWZqWwZnpgemhFa6h0^Go5IjKObe z4F9j;-;e&k1O9(?|DT5ccd-sQa|Oqoob>n8 zc)@z>bLEh$#;Mp{l5M;h+3o7?&zJxE#`!Fm z2=i%k?YrAtlkd$P%9Z8wrKkZ1Nu^Kmz4ZI-Y#Qb`g)>mu8K?@!n((i!23!yRjo^PZ zzD096_Eye7B(K_0Zp|!E{dW1!_n_1Jn5)oNesusY^euKibca)YXW(OVP#5!mx_f_S zwB`?eWM=dq4&1K5?Yx42u>Z-6#R?8-<;L*1=Rg6f=c9BT8GBC-L>&6P=dq8ha|X7@cvqUi9?f{2 z9Ct_MWooC2!G`0JKdeXexYrv3|BmplCC9s;j@wGYuGHWY-tRNL&ewWQZmAo+|02DA zlJA`jPw^WwA^VBX&N%M{_zd`XJPZH8|7@341IomISz0Fkw{J4X{d6zB{Nws} z&AL2j_Ep37ayO&k{}{aPhudEF|5N*RsyhqwDozB2k z*uO)*f`8e>)P6rYSGhg^2l)WmJe!*Ee~iAlAO3IgQ+JT*?)GE2e-Fep%q4luQ9U_C zUE*y%pS`hf|7J$PIo$kSe24GMCoIa}E6MJgtzWM!NsDL0g!wR;=OD~_tQhk`12;U( zz-f@TjyTZW_%qBHX~K47@CDnL0iQ5eAfcbS6I-C4J`4YG*~5IO!S3WBJ0{x8%iyz@ zR5Kbi&V0A9m(fn!hQ;+IR^X4MvcFgGH~0_U5Aghl(%X1<_w0m!Gx*Pf|1`X?4pG@3p5qVR0{h#XfoI_V zjymvJ*e~MeZj>X)iE~viapv)}=Hz|`{{r$rb|I_pt>4;0Tx;)O{{`}_ljT{&T6j!h zrs9D|AC|*rbA}&R{|~d9bJDjLVDDVK{zDY74|e~=fwFpIgW%Qtl6eNUcgfTNNoPWz zs2aqks&P$l!GYRwU-1474dR&Rv0mo5yxYp$=XPdGMBZ0tIjtMm|2}kOUpW_da@JtE zfm_Y7{hOI2O`YvpIAS}aBl?K)uPO`A>hLNbjJ}D`F}v`ojDH*mrgR?P6SZul!{mv)jhuYtQ7rO^N5U?mWNV z{Z~6X*i<~IFQz~tHsKxkPs0N#&d+%c{>R|Y*4>_Jrc*(^6~Dt@ulqf_owa82Oyl7H zbbRm2zIiA9*K9WSWjU6y?7%3Tq=nJ8?AgWW`o3QeF_V6ya&w%cAEOiY3xz!}LGH0z z`kp-HNZ^B(i*-Rb14(DfJ0I!r&#VdLDpeczyIx!i81j2#`2%yDF0@p`Z)e7AWCmd8 zcpuo2l-^?YJ~(jw;D57ybvN1{-kY5~EB95w4DcNEY*Dy}IS--VH&y*lFHp9S0|@#* z?0HI3XDCJrsR0(iJqq*B^6~AjNxZgTuz0j5j&Cpdp?=OofA&C4zjAZ-skmJA!MM(F zw)koB#?R()+OH)zWtME@=-mQYwTn; z$D{B)>%~&})rcpkVLj*!%#Uk98;!~w>SLpF;@LWPw*``n!VYuL3rQA_OSNdgJU!S=4?!ojep1+*)*p>aAtpTv_1pCVJRC&y@J%>Mi z)bsyFT`HS%|EGB4EBJrzxu1^xp_^MAR5#zlKh|5nq*^rehT-r(3IBTV5BjaB*k>C2 zUxYt?OmYMGx28i|z(4GO4L(4atsVMOO0$cviX}S2e^RXT7Yvx>4BQI;aWU3G_kUG+ z9sGy*o_C#ziN3EIZuoS$z0rKTd!v)zgr3hQ=>MnTe;WR|)rg9zPv^t8ug0F=&UTLG zXZ|<+|D>67p>|SH&h28{V|x)7c>0nN^4jCn23CrBzLD!GOt)s{_?whl>F$4LwxoTK z?tQr9+qxt{y z7XIzb80nW_;i2u98zlS*(ec|7m4H)9QUJL&Wyzl&;SxI?fG5Y)RTE8TdGVnLY zDC_!sx~ zbMuE7|A)&3z4x1Vce^-0%;&hsC;EzCycPDl#K$|-r4n%Iu73Sxbg^6=^(_11`217L zEQi_C`qRqFfreP`YB-nyku%JG2z8*Qa>A*eMUKR{5o5&S6VyFN@WXq0&5nL{F`w@V z`hI9^^Fs{qdDn-@U2M=u@{HcUUTpsovB1r2!8?4G1iQ5aZrgFgu5z|>(vyFx1LXG{ zx{K}Zq4%fCG2A4c9!U?Z5zD^m&tI#jFT=&WnSVRo?BG2T0Y=xc0p0OOd(ex0#EgAOKR<()e{QguKoiWR&d#3e->OxC z|6f)tm&%T$xOe=%y(RJ9OX+P6U-mQ~0`bEv0F|*OGp^NLT6XV^gMsTB@ zz}@wr*N~4dC|=3wef?z}tTNo)M!5Rw zf#oXW2G|w2p=@IREAPW_F8s@6;qTtVJ`?|t18xHU(6ees(1<1Qp9=p8?&k!)z*su} zZTL@w|6uq(PbRwiV@cpYKS^fB^{53i7RG0w$-%MFfp>PpeIv{sbxS|I5R_0{kn(-_Ah&fY}b$f`1*)xgq=; z{RjTnz`wJ(@Ll151N?i_0RewAV(_glpS#7(3f#f2VTO+$u3Aa(ugKRf%Z{YF`K=u`VaX--t2qN zP;B4pbR7Q$pMTVFzTT7H!T(Se_MgIjUo4dl^b!L$g#VIgrg}uce{suH_-|p;7sdI8 zaQ{QjzgNz-L?6KQSMX03^HzlaR+yOS`NuH$PvfgR4gYB{A4$|ZM!ig4fcN z3y)5Yd0=Dvg#N0JmUw=TCO7cM=7f=J1HDFqs&8;8nfOYR*?f$ov3y$F55&m7=3wzJT>GoSNa_2-{U`6~W%|0`t_ ziSv|ewwOV>l-)94GSp&|3J;3q_4$m;$X}g>1AC>R`?+a(3p0hJl56M5& zT<`NapNV@uLw&ES^U^gc2LGaF)aY-?zX|^0h&_SlUB>;fzeJw*f8!te!ouCZBANHU zl6;W;ALm0ZDBvG-fO#90aYX6Cw2RH*-$pIGLmUUR>D`SF&?EK%>wR?t{BMH4n0@~3 z`Zfy47n)1a$lL_+>*ZdDM7k|g1I>QUNQO57iMgv>4erYintNjku+ z`I8A}c(8t!o8%^%J4;2(KRqV)IRJa}+uz;m8HF0(20ri^KHm{O|7Y%d@cEAt``thO zfXu_sybjl*X>@IuXYn!1ncdOEXQ}4*$-mW^g@<224@^CHw}5|;W@do3@b?V(hnXdL z;`{&5)O+2_Q2!n7GYx@%e|XJ=`#3Una z@6CK7_yXf%-TzvL67UaPe>*YU%A zGoqNEb?4(hihVcmpFWWjvnQ=x*vYd)oPQjb_XN(t0hoUlW1nl^Njb#*=ml;mCm3?(=jr};+{3~0 zEB_6fN8o=S{O^SSGW7uS*+)$v`XW=OlV@T7K%6VQc4C;*`c#}J2(jR!C)bPh(r|L> ztBtHy4?gYA7DybMKbU?PC~q{y&xM_t<|^a*Ec~g9*l#G_%>(>?_Qss47nAd{r^)yo zN#dA%dIoQndtL2yO2#?=hZ>a-JB8lwYT{XZ^VR-LGV!-7&;1X5!2$c=3m!XOUQVE* z`+ub#5Ng6ToPj#@KwbG@eAdoQ#PS(x!foV(+KU6PV?DHmd@)dRj&eJ#Y0x`>Gci`ZC=-K`(2U|HlYpV|i9ndtf=D%(< zXJ!C?QcJw_Z1$s@b35ftdEJe2dmlv^gWkt8{Sx-{=El8n|I}Uo0+uJpG5y$^$Sm@N zog{iEvp*dToE&;UlkAgfEN9t_tt%|=Fwgh9-$%^6?${jm=C4!?IY8VOvw+%^EGg${ zM%I?M;NPCk85MK1hSePIOy2?X7vTSxuT#jl`2N>Oodd(kWNLKnMEfpsj`Ut16)Qd` zA8@-IIo)-8S#gV5!h83|b%3e;7Q+8-_|JEC66!s#saw!nKehF`ed4%p@`vWuls|1I zpgjyH3njwdx!V`u-nDy79y>dZm$_>nhFV`4T$M2YAx$4Rj`iw_iE1}gisha>mG}VR z420hBpd$kQsqnWy=WwVA23`PPuys{uKs@k+UV)_ga>9++=;nOYHi=5VC&b0qveCf? z^mGPpgugi(dUFFmWaR^c#nAfF5?}DIeu^$k{lzo*7C-4<@-|C+{wcl&uJqmX;}G@n z89sZ4vu1{4*+%x0v{k?DYtBkfGik~v7N?rmoN8vnpf}z3fcqx7z5U|q@1krU9EbZ> z@(CR6bieI1`BLt51(`-BsFUo#PuYok`(88*zxnL4Ch%_s|B^BOk9NL-e~A6V4ET@- zZ0GqjmLryzZjc23aR0*&pKYeX55or^;QR|@f-^of)&bknC&GU${M(uv z+tuf{2kYRw@b51cRNG0-E$>&_8Teiyy_Wj-c+(3T3yx~-wzHI#R>wdF8%8caa&CK35WSa*J8N9V0GeWQ2q@1NuEj^r;)A&bZxhpTgaPmSHru_EH67a|Iy}K znE#8c^$hJF82JMH*L$6#@c$`FH-G-@vO4lrP2iub@9|x_Gn@RMomJaH4`@>&_G9CU z=WH$(y#)X4@PAS5J@9YrR+~1?{T~D8yR-1W*WDjQ#=(EKa~;9{MH2S5Pm1|M#d983 zHyQx{S@53^|7qkB?AO5lc`?EmdSiH;FDN+j4!E~t=WmSr)lM#06~{LhO5`+u@1*-4 z_JuaE^I>UpLjT~Jlf~5eN~-tioe*~)|1r#dC?s_^z8>HiP@_vE>PzgR$R zXnXB)SvtU8&T4i-RI5U2@xN=!Th_;!&?lHb*nn1SKsz>|ll;MTa=~3??nnAEnz>7P^?Q8f?vJF8 z?}q4du{lIie&1O9(?w%5Qv*sVeA)%E`VBmI4yn$Yj`{~a)2 zMh?;=h1GqYqd(*o`VW$)xC{1=>NR*+o$m?p&Qxb@yPBq5GAjn+M&Sy6zAM@}ExY)> zAPF;@OT_Vhu=@{G54qi7+smodl}ikhgDt$h{QuxzczZ>E{E zouVtF(l_q)+0S^-r*IvE(A&#Tcut4#@4_y&Z}{ZL?O|(zm)t=evUK9?GyC1wSH+Ao z<>yzb^(`gq_}+HSPWlb@;S4NepLeWX zeJPjOfPjC)#I78b^*DYFQ~u@2bkDLe9nco`33tD_;!O^FHLoo{v)C{%BhvDByy{b9<>%^Rq4Zq}& z=dc6dhqoqPw3TM(?k2e!ulh&U;3=#AtaF%ib$ax7+VGJCy~oIq_mL2B28OHF4W^uh)k5PX1U zF*mqi)DCYe?Af^v{+(h!FwBde(1jjvHBgSHJ8ttzdD30{^}}rC_vB}JfM4j)^Xhe% z*}!aek^gRf=5J<1e`B8XK{>IrV!dB*`FFWL^mvX>$=BpF_`mD!FN1lIPl&mi4dl_| zGjJ6CYvj!Ne2bnU<2}!Szuj|xb%%b}?_=-#u>2j>GH}XUwT|H16q@AX94HgrHF zZ{C_e&b28J*CbjuuKSB|CVYOz2pAx9K!K^0P&gg=L`HRa{Q83_G?p;xdWAK+^EHWw+|qp9ar zy`XMw_H9dj1*@-;qt9Vy`mxy8&%}NQ?B9y94|`1a-?k?9Bi1Gr6`$sW{~0=AGaWfs zkB{A1izdQ;s@P&CZlwJ=e+~Ob^;A#Z0skKGcQ%jcb^NrthOS?ub$^CESk2^tTG0dT z;D5c`cUyk7{zHAP;cgZjc^CYL!2fRePovWU_VdWoL_f&gFFFHnlF6|Dhzu9^t&`U~ zL8id{oPCKmlK-*~W|n;KQt`~^`hH632R;<%_>Wb&OAgz9Z}aJ%&-sO1|Bv)UJ~`RE z^5S{%D)Q;=GB@J)qUK=~at?Ci)Rv+H{*C{=azoYLvJ{F}nRCH&jLzdauy#DIAEwY$UrM$h&- z_;*)h{a8%%FY=i)aD<$c6Zl=+nh#&0fZSd_x}^XfN)EoOU3f+CSYJ0Y^4*ZrJt?0n z-;pEiYTroS%))=0SmFTu&zYN41czF`1s^cX-yQK>=#3l}_rzV^^ENxO4gM45(eGqi zUpB(G|(0>&T9vb7n zEj{>pFTwmZct66gTSWG|`-6zt+;#wF;xBgFev%y@=+4_UG2L%pQ}_6_~(QF zRq!tff8YOl9{3-GzrDy0?tuR?*dKuX9zdy>cEjOJ}h~$NTi2axF^M~1+aU=Zi Oh5uaG?`2CbM*kmXvmjIe literal 0 HcmV?d00001 diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py new file mode 100644 index 000000000..9c15de804 --- /dev/null +++ b/Tests/test_file_spider.py @@ -0,0 +1,15 @@ +from tester import * + +from PIL import Image + +test_file = "Tests/images/lena.spider" + + +def test_sanity(): + im = Image.open(test_file) + im.load() + assert_equal(im.mode, "F") + assert_equal(im.size, (128, 128)) + assert_equal(im.format, "SPIDER") + +# End of file From 5f9cad1a8eea23254889788222c92cf20a28d829 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 26 May 2014 20:15:48 +0300 Subject: [PATCH 14/33] pyflakes --- PIL/SpiderImagePlugin.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/PIL/SpiderImagePlugin.py b/PIL/SpiderImagePlugin.py index cd97c524d..ec8267f22 100644 --- a/PIL/SpiderImagePlugin.py +++ b/PIL/SpiderImagePlugin.py @@ -74,11 +74,9 @@ def isSpiderImage(filename): fp = open(filename,'rb') f = fp.read(92) # read 23 * 4 bytes fp.close() - bigendian = 1 t = struct.unpack('>23f',f) # try big-endian first hdrlen = isSpiderHeader(t) if hdrlen == 0: - bigendian = 0 t = struct.unpack('<23f',f) # little-endian hdrlen = isSpiderHeader(t) return hdrlen From 14c8d8afe4d01275d2d8973e53c7ab4e32fd3d31 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 26 May 2014 21:00:20 +0300 Subject: [PATCH 15/33] More spider tests --- Tests/test_file_spider.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 9c15de804..d93724492 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -1,6 +1,7 @@ from tester import * from PIL import Image +from PIL import SpiderImagePlugin test_file = "Tests/images/lena.spider" @@ -12,4 +13,24 @@ def test_sanity(): assert_equal(im.size, (128, 128)) assert_equal(im.format, "SPIDER") + +def test_save(): + # Arrange + temp = tempfile('temp.spider') + im = lena() + + # Act + im.save(temp, "SPIDER") + + # Assert + im2 = Image.open(temp) + assert_equal(im2.mode, "F") + assert_equal(im2.size, (128, 128)) + assert_equal(im2.format, "SPIDER") + + +def test_isSpiderImage(): + assert_true(SpiderImagePlugin.isSpiderImage(test_file)) + + # End of file From 5217a523d677067cf3d268b1941f0fb47ca75de3 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 26 May 2014 21:18:48 +0300 Subject: [PATCH 16/33] Make sure nvalues is int and not float for Python 3 --- PIL/SpiderImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PIL/SpiderImagePlugin.py b/PIL/SpiderImagePlugin.py index ec8267f22..0810b929e 100644 --- a/PIL/SpiderImagePlugin.py +++ b/PIL/SpiderImagePlugin.py @@ -208,7 +208,7 @@ def makeSpiderHeader(im): if 1024%lenbyt != 0: labrec += 1 labbyt = labrec * lenbyt hdr = [] - nvalues = labbyt / 4 + nvalues = int(labbyt / 4) for i in range(nvalues): hdr.append(0.0) From eacbd7b04abac95a2f84a3f091642530910f450f Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 26 May 2014 21:56:40 +0300 Subject: [PATCH 17/33] pep8/pyflakes --- PIL/SpiderImagePlugin.py | 80 ++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/PIL/SpiderImagePlugin.py b/PIL/SpiderImagePlugin.py index 0810b929e..306b348bc 100644 --- a/PIL/SpiderImagePlugin.py +++ b/PIL/SpiderImagePlugin.py @@ -36,17 +36,23 @@ from __future__ import print_function from PIL import Image, ImageFile -import os, struct, sys +import os +import struct +import sys + def isInt(f): try: i = int(f) - if f-i == 0: return 1 - else: return 0 + if f-i == 0: + return 1 + else: + return 0 except: return 0 -iforms = [1,3,-11,-12,-21,-22] +iforms = [1, 3, -11, -12, -21, -22] + # There is no magic number to identify Spider files, so just check a # series of header locations to see if they have reasonable values. @@ -56,28 +62,32 @@ iforms = [1,3,-11,-12,-21,-22] def isSpiderHeader(t): h = (99,) + t # add 1 value so can use spider header index start=1 # header values 1,2,5,12,13,22,23 should be integers - for i in [1,2,5,12,13,22,23]: - if not isInt(h[i]): return 0 + for i in [1, 2, 5, 12, 13, 22, 23]: + if not isInt(h[i]): + return 0 # check iform iform = int(h[5]) - if not iform in iforms: return 0 + if iform not in iforms: + return 0 # check other header values labrec = int(h[13]) # no. records in file header labbyt = int(h[22]) # total no. of bytes in header lenbyt = int(h[23]) # record length in bytes - #print "labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt) - if labbyt != (labrec * lenbyt): return 0 + # print "labrec = %d, labbyt = %d, lenbyt = %d" % (labrec,labbyt,lenbyt) + if labbyt != (labrec * lenbyt): + return 0 # looks like a valid header return labbyt + def isSpiderImage(filename): - fp = open(filename,'rb') + fp = open(filename, 'rb') f = fp.read(92) # read 23 * 4 bytes fp.close() - t = struct.unpack('>23f',f) # try big-endian first + t = struct.unpack('>23f', f) # try big-endian first hdrlen = isSpiderHeader(t) if hdrlen == 0: - t = struct.unpack('<23f',f) # little-endian + t = struct.unpack('<23f', f) # little-endian hdrlen = isSpiderHeader(t) return hdrlen @@ -94,11 +104,11 @@ class SpiderImageFile(ImageFile.ImageFile): try: self.bigendian = 1 - t = struct.unpack('>27f',f) # try big-endian first + t = struct.unpack('>27f', f) # try big-endian first hdrlen = isSpiderHeader(t) if hdrlen == 0: self.bigendian = 0 - t = struct.unpack('<27f',f) # little-endian + t = struct.unpack('<27f', f) # little-endian hdrlen = isSpiderHeader(t) if hdrlen == 0: raise SyntaxError("not a valid Spider file") @@ -110,7 +120,7 @@ class SpiderImageFile(ImageFile.ImageFile): if iform != 1: raise SyntaxError("not a Spider 2D image") - self.size = int(h[12]), int(h[2]) # size in pixels (width, height) + self.size = int(h[12]), int(h[2]) # size in pixels (width, height) self.istack = int(h[24]) self.imgnumber = int(h[27]) @@ -139,9 +149,10 @@ class SpiderImageFile(ImageFile.ImageFile): self.rawmode = "F;32F" self.mode = "F" - self.tile = [("raw", (0, 0) + self.size, offset, - (self.rawmode, 0, 1))] - self.__fp = self.fp # FIXME: hack + self.tile = [ + ("raw", (0, 0) + self.size, offset, + (self.rawmode, 0, 1))] + self.__fp = self.fp # FIXME: hack # 1st image index is zero (although SPIDER imgnumber starts at 1) def tell(self): @@ -174,6 +185,7 @@ class SpiderImageFile(ImageFile.ImageFile): from PIL import ImageTk return ImageTk.PhotoImage(self.convert2byte(), palette=256) + # -------------------------------------------------------------------- # Image series @@ -198,14 +210,16 @@ def loadImageSeries(filelist=None): imglist.append(im) return imglist + # -------------------------------------------------------------------- # For saving images in Spider format def makeSpiderHeader(im): - nsam,nrow = im.size + nsam, nrow = im.size lenbyt = nsam * 4 # There are labrec records in the header labrec = 1024 / lenbyt - if 1024%lenbyt != 0: labrec += 1 + if 1024 % lenbyt != 0: + labrec += 1 labbyt = labrec * lenbyt hdr = [] nvalues = int(labbyt / 4) @@ -216,13 +230,13 @@ def makeSpiderHeader(im): return [] # NB these are Fortran indices - hdr[1] = 1.0 # nslice (=1 for an image) - hdr[2] = float(nrow) # number of rows per slice - hdr[5] = 1.0 # iform for 2D image - hdr[12] = float(nsam) # number of pixels per line - hdr[13] = float(labrec) # number of records in file header - hdr[22] = float(labbyt) # total number of bytes in header - hdr[23] = float(lenbyt) # record length in bytes + hdr[1] = 1.0 # nslice (=1 for an image) + hdr[2] = float(nrow) # number of rows per slice + hdr[5] = 1.0 # iform for 2D image + hdr[12] = float(nsam) # number of pixels per line + hdr[13] = float(labrec) # number of records in file header + hdr[22] = float(labbyt) # total number of bytes in header + hdr[23] = float(lenbyt) # record length in bytes # adjust for Fortran indexing hdr = hdr[1:] @@ -230,9 +244,10 @@ def makeSpiderHeader(im): # pack binary data into a string hdrstr = [] for v in hdr: - hdrstr.append(struct.pack('f',v)) + hdrstr.append(struct.pack('f', v)) return hdrstr + def _save(im, fp, filename): if im.mode[0] != "F": im = im.convert('F') @@ -248,11 +263,12 @@ def _save(im, fp, filename): raise IOError("Unable to open %s for writing" % filename) fp.writelines(hdr) - rawmode = "F;32NF" #32-bit native floating point - ImageFile._save(im, fp, [("raw", (0,0)+im.size, 0, (rawmode,0,1))]) + rawmode = "F;32NF" # 32-bit native floating point + ImageFile._save(im, fp, [("raw", (0, 0)+im.size, 0, (rawmode, 0, 1))]) fp.close() + def _save_spider(im, fp, filename): # get the filename extension and register it with Image fn, ext = os.path.splitext(filename) @@ -290,5 +306,7 @@ if __name__ == "__main__": if outfile != "": # perform some image operation im = im.transpose(Image.FLIP_LEFT_RIGHT) - print("saving a flipped version of %s as %s " % (os.path.basename(filename), outfile)) + print( + "saving a flipped version of %s as %s " % + (os.path.basename(filename), outfile)) im.save(outfile, "SPIDER") From ae37743ac743bc4f23cc7caa787a541fdb3e9651 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 27 May 2014 16:58:26 +0300 Subject: [PATCH 18/33] Both kinds of bounding box for arc, chord and pieslice. --- Tests/test_imagedraw.py | 6 +-- _imaging.c | 109 ++++++++++++++++++++++++++++++---------- 2 files changed, 86 insertions(+), 29 deletions(-) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 2e0cc07c0..c47638a05 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -62,7 +62,7 @@ def helper_arc(bbox): def test_arc1(): - assert_exception(TypeError, lambda: helper_arc(bbox1)) + helper_arc(bbox1) def test_arc2(): @@ -97,7 +97,7 @@ def helper_chord(bbox): def test_chord1(): - assert_exception(TypeError, lambda: helper_chord(bbox1)) + helper_chord(bbox1) def test_chord2(): @@ -160,7 +160,7 @@ def helper_pieslice(bbox): def test_pieslice1(): - assert_exception(TypeError, lambda: helper_pieslice(bbox1)) + helper_pieslice(bbox1) def test_pieslice2(): diff --git a/_imaging.c b/_imaging.c index c47868b81..f8fd96f8a 100644 --- a/_imaging.c +++ b/_imaging.c @@ -2252,17 +2252,17 @@ void _font_text_asBytes(PyObject* encoded_string, unsigned char** text){ if (bytes) { *text = (unsigned char*)PyBytes_AsString(bytes); return; - } + } #if PY_VERSION_HEX < 0x03000000 /* likely case here is py2.x with an ordinary string. but this isn't defined in Py3.x */ if (PyString_Check(encoded_string)) { *text = (unsigned char *)PyString_AsString(encoded_string); - } + } #endif } - + static PyObject* _font_getmask(ImagingFontObject* self, PyObject* args) @@ -2336,7 +2336,7 @@ _font_getsize(ImagingFontObject* self, PyObject* args) return NULL; } - return Py_BuildValue("ii", textwidth(self, text), self->ysize); + return Py_BuildValue("ii", textwidth(self, text), self->ysize); } static struct PyMethodDef _font_methods[] = { @@ -2399,17 +2399,35 @@ _draw_ink(ImagingDrawObject* self, PyObject* args) static PyObject* _draw_arc(ImagingDrawObject* self, PyObject* args) { - int x0, y0, x1, y1; + double* xy; + int n; + + PyObject* data; int ink; int start, end; int op = 0; - if (!PyArg_ParseTuple(args, "(iiii)iii|i", - &x0, &y0, &x1, &y1, - &start, &end, &ink)) + if (!PyArg_ParseTuple(args, "Oiii|i", &data, &start, &end, &ink)) return NULL; - if (ImagingDrawArc(self->image->image, x0, y0, x1, y1, start, end, - &ink, op) < 0) + n = PyPath_Flatten(data, &xy); + if (n < 0) + return NULL; + if (n != 2) { + PyErr_SetString(PyExc_TypeError, + "coordinate list must contain exactly 2 coordinates" + ); + return NULL; + } + + n = ImagingDrawArc(self->image->image, + (int) xy[0], (int) xy[1], + (int) xy[2], (int) xy[3], + start, end, &ink, op + ); + + free(xy); + + if (n < 0) return NULL; Py_INCREF(Py_None); @@ -2455,15 +2473,35 @@ _draw_bitmap(ImagingDrawObject* self, PyObject* args) static PyObject* _draw_chord(ImagingDrawObject* self, PyObject* args) { - int x0, y0, x1, y1; + double* xy; + int n; + + PyObject* data; int ink, fill; int start, end; - if (!PyArg_ParseTuple(args, "(iiii)iiii", - &x0, &y0, &x1, &y1, &start, &end, &ink, &fill)) + if (!PyArg_ParseTuple(args, "Oiiii", + &data, &start, &end, &ink, &fill)) return NULL; - if (ImagingDrawChord(self->image->image, x0, y0, x1, y1, - start, end, &ink, fill, self->blend) < 0) + n = PyPath_Flatten(data, &xy); + if (n < 0) + return NULL; + if (n != 2) { + PyErr_SetString(PyExc_TypeError, + "coordinate list must contain exactly 2 coordinates" + ); + return NULL; + } + + n = ImagingDrawChord(self->image->image, + (int) xy[0], (int) xy[1], + (int) xy[2], (int) xy[3], + start, end, &ink, fill, self->blend + ); + + free(xy); + + if (n < 0) return NULL; Py_INCREF(Py_None); @@ -2492,8 +2530,8 @@ _draw_ellipse(ImagingDrawObject* self, PyObject* args) return NULL; } - n = ImagingDrawEllipse(self->image->image, - (int) xy[0], (int) xy[1], + n = ImagingDrawEllipse(self->image->image, + (int) xy[0], (int) xy[1], (int) xy[2], (int) xy[3], &ink, fill, self->blend ); @@ -2656,15 +2694,34 @@ _draw_outline(ImagingDrawObject* self, PyObject* args) static PyObject* _draw_pieslice(ImagingDrawObject* self, PyObject* args) { - int x0, y0, x1, y1; + double* xy; + int n; + + PyObject* data; int ink, fill; int start, end; - if (!PyArg_ParseTuple(args, "(iiii)iiii", - &x0, &y0, &x1, &y1, &start, &end, &ink, &fill)) + if (!PyArg_ParseTuple(args, "Oiiii", &data, &start, &end, &ink, &fill)) return NULL; - if (ImagingDrawPieslice(self->image->image, x0, y0, x1, y1, - start, end, &ink, fill, self->blend) < 0) + n = PyPath_Flatten(data, &xy); + if (n < 0) + return NULL; + if (n != 2) { + PyErr_SetString(PyExc_TypeError, + "coordinate list must contain exactly 2 coordinates" + ); + return NULL; + } + + n = ImagingDrawPieslice(self->image->image, + (int) xy[0], (int) xy[1], + (int) xy[2], (int) xy[3], + start, end, &ink, fill, self->blend + ); + + free(xy); + + if (n < 0) return NULL; Py_INCREF(Py_None); @@ -2738,9 +2795,9 @@ _draw_rectangle(ImagingDrawObject* self, PyObject* args) return NULL; } - n = ImagingDrawRectangle(self->image->image, + n = ImagingDrawRectangle(self->image->image, (int) xy[0], (int) xy[1], - (int) xy[2], (int) xy[3], + (int) xy[2], (int) xy[3], &ink, fill, self->blend ); @@ -3117,8 +3174,8 @@ _getattr_ptr(ImagingObject* self, void* closure) static PyObject* _getattr_unsafe_ptrs(ImagingObject* self, void* closure) { - return Py_BuildValue("(ss)(si)(si)(si)(si)(si)(sn)(sn)(sn)(sn)(sn)(si)(si)(sn)", - "mode", self->image->mode, + return Py_BuildValue("(ss)(si)(si)(si)(si)(si)(sn)(sn)(sn)(sn)(sn)(si)(si)(sn)", + "mode", self->image->mode, "type", self->image->type, "depth", self->image->depth, "bands", self->image->bands, From 4dcae4402cca5ed75e33c038778c291691095a7d Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 27 May 2014 23:55:43 +0300 Subject: [PATCH 19/33] Add back the other bounding box [CI skip] --- docs/reference/ImageDraw.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 45be43057..30aa15a9b 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -91,11 +91,11 @@ Methods Draws an arc (a portion of a circle outline) between the start and end angles, inside the given bounding box. - :param xy: Four points to define the bounding box. Sequence of - ``[x0, y0, x1, y1]``. - :param start: Starting angle, in degrees. Angles are measured from + :param xy: Four points to define the bounding box. Sequence of + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. + :param start: Starting angle, in degrees. Angles are measured from 3 o'clock, increasing clockwise. - :param end: Ending angle, in degrees. + :param end: Ending angle, in degrees. :param fill: Color to use for the arc. .. py:method:: PIL.ImageDraw.Draw.bitmap(xy, bitmap, fill=None) @@ -114,8 +114,8 @@ Methods Same as :py:meth:`~PIL.ImageDraw.Draw.arc`, but connects the end points with a straight line. - :param xy: Four points to define the bounding box. Sequence of - ``[x0, y0, x1, y1]``. + :param xy: Four points to define the bounding box. Sequence of + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. :param outline: Color to use for the outline. :param fill: Color to use for the fill. @@ -148,7 +148,7 @@ Methods center of the bounding box. :param xy: Four points to define the bounding box. Sequence of - ``[x0, y0, x1, y1]``. + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. :param outline: Color to use for the outline. :param fill: Color to use for the fill. From 085527e01db9ae6cbbefbd278ab0ebfd8d94c998 Mon Sep 17 00:00:00 2001 From: wiredfool Date: Fri, 30 May 2014 08:47:26 -0700 Subject: [PATCH 20/33] Updated Changes.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index b19ef359d..a8dd7d137 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Added tests for Spider files + [hugovk] + - Use libtiff to write any compressed tiff files [wiredfool] From 89130203b1f583fecf398fb65ef42e3f8e1533db Mon Sep 17 00:00:00 2001 From: wiredfool Date: Sun, 1 Jun 2014 09:10:52 -0700 Subject: [PATCH 21/33] Update CHANGES.rst [ci-skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a8dd7d137..6b7d0162f 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -3,6 +3,9 @@ Changelog (Pillow) 2.5.0 (unreleased) ------------------ +- Added more ImageDraw tests + [hugovk] + - Added tests for Spider files [hugovk] From 27d49b6f27046221da93236963950c3bb2b07d77 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 2 Jun 2014 09:57:49 +0300 Subject: [PATCH 22/33] pep8 --- PIL/ImageCms.py | 225 ++++++++++++++++++++++++++++-------------------- 1 file changed, 132 insertions(+), 93 deletions(-) diff --git a/PIL/ImageCms.py b/PIL/ImageCms.py index 5decaf704..fc176952e 100644 --- a/PIL/ImageCms.py +++ b/PIL/ImageCms.py @@ -66,7 +66,8 @@ pyCMS Added try/except statements arount type() checks of potential CObjects... Python won't let you use type() - on them, and raises a TypeError (stupid, if you ask me!) + on them, and raises a TypeError (stupid, if you ask + me!) Added buildProofTransformFromOpenProfiles() function. Additional fixes in DLL, see DLL code for details. @@ -115,7 +116,9 @@ FLAGS = { "MATRIXOUTPUT": 2, "MATRIXONLY": (1 | 2), "NOWHITEONWHITEFIXUP": 4, # Don't hot fix scum dot - "NOPRELINEARIZATION": 16, # Don't create prelinearization tables on precalculated transforms (internal use) + # Don't create prelinearization tables on precalculated transforms + # (internal use): + "NOPRELINEARIZATION": 16, "GUESSDEVICECLASS": 32, # Guess device class (for transform2devicelink) "NOTCACHE": 64, # Inhibit 1-pixel cache "NOTPRECALC": 256, @@ -168,13 +171,14 @@ class ImageCmsProfile: class ImageCmsTransform(Image.ImagePointHandler): + """Transform. This can be used with the procedural API, or with the standard Image.point() method. """ def __init__(self, input, output, input_mode, output_mode, - intent=INTENT_PERCEPTUAL, - proof=None, proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0): + intent=INTENT_PERCEPTUAL, proof=None, + proof_intent=INTENT_ABSOLUTE_COLORIMETRIC, flags=0): if proof is None: self.transform = core.buildTransform( input.profile, output.profile, @@ -238,11 +242,15 @@ def get_display_profile(handle=None): # --------------------------------------------------------------------. class PyCMSError(Exception): - """ (pyCMS) Exception class. This is used for all errors in the pyCMS API. """ + + """ (pyCMS) Exception class. + This is used for all errors in the pyCMS API. """ pass -def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL, outputMode=None, inPlace=0, flags=0): +def profileToProfile( + im, inputProfile, outputProfile, renderingIntent=INTENT_PERCEPTUAL, + outputMode=None, inPlace=0, flags=0): """ (pyCMS) Applies an ICC transformation to a given image, mapping from inputProfile to outputProfile. @@ -264,29 +272,33 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER profiles, the input profile must handle RGB data, and the output profile must handle CMYK data. - :param im: An open PIL image object (i.e. Image.new(...) or Image.open(...), etc.) - :param inputProfile: String, as a valid filename path to the ICC input profile - you wish to use for this image, or a profile object + :param im: An open PIL image object (i.e. Image.new(...) or + Image.open(...), etc.) + :param inputProfile: String, as a valid filename path to the ICC input + profile you wish to use for this image, or a profile object :param outputProfile: String, as a valid filename path to the ICC output profile you wish to use for this image, or a profile object - :param renderingIntent: Integer (0-3) specifying the rendering intent you wish - to use for the transform + :param renderingIntent: Integer (0-3) specifying the rendering intent you + wish to use for the transform INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) - see the pyCMS documentation for details on rendering intents and what they do. - :param outputMode: A valid PIL mode for the output image (i.e. "RGB", "CMYK", - etc.). Note: if rendering the image "inPlace", outputMode MUST be the - same mode as the input, or omitted completely. If omitted, the outputMode - will be the same as the mode of the input image (im.mode) - :param inPlace: Boolean (1 = True, None or 0 = False). If True, the original - image is modified in-place, and None is returned. If False (default), a - new Image object is returned with the transform applied. + see the pyCMS documentation for details on rendering intents and what + they do. + :param outputMode: A valid PIL mode for the output image (i.e. "RGB", + "CMYK", etc.). Note: if rendering the image "inPlace", outputMode + MUST be the same mode as the input, or omitted completely. If + omitted, the outputMode will be the same as the mode of the input + image (im.mode) + :param inPlace: Boolean (1 = True, None or 0 = False). If True, the + original image is modified in-place, and None is returned. If False + (default), a new Image object is returned with the transform applied. :param flags: Integer (0-...) specifying additional flags - :returns: Either None or a new PIL image object, depending on value of inPlace + :returns: Either None or a new PIL image object, depending on value of + inPlace :exception PyCMSError: """ @@ -297,7 +309,8 @@ def profileToProfile(im, inputProfile, outputProfile, renderingIntent=INTENT_PER raise PyCMSError("renderingIntent must be an integer between 0 and 3") if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): - raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG) + raise PyCMSError( + "flags must be an integer between 0 and %s" + _MAX_FLAG) try: if not isinstance(inputProfile, ImageCmsProfile): @@ -329,8 +342,8 @@ def getOpenProfile(profileFilename): If profileFilename is not a vaild filename for an ICC profile, a PyCMSError will be raised. - :param profileFilename: String, as a valid filename path to the ICC profile you - wish to open, or a file-like object. + :param profileFilename: String, as a valid filename path to the ICC profile + you wish to open, or a file-like object. :returns: A CmsProfile class object. :exception PyCMSError: """ @@ -341,7 +354,9 @@ def getOpenProfile(profileFilename): raise PyCMSError(v) -def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent=INTENT_PERCEPTUAL, flags=0): +def buildTransform( + inputProfile, outputProfile, inMode, outMode, + renderingIntent=INTENT_PERCEPTUAL, flags=0): """ (pyCMS) Builds an ICC transform mapping from the inputProfile to the outputProfile. Use applyTransform to apply the transform to a given @@ -374,14 +389,14 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent manually overridden if you really want to, but I don't know of any time that would be of use, or would even work). - :param inputProfile: String, as a valid filename path to the ICC input profile - you wish to use for this transform, or a profile object + :param inputProfile: String, as a valid filename path to the ICC input + profile you wish to use for this transform, or a profile object :param outputProfile: String, as a valid filename path to the ICC output profile you wish to use for this transform, or a profile object - :param inMode: String, as a valid PIL mode that the appropriate profile also - supports (i.e. "RGB", "RGBA", "CMYK", etc.) - :param outMode: String, as a valid PIL mode that the appropriate profile also - supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param inMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param outMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) :param renderingIntent: Integer (0-3) specifying the rendering intent you wish to use for the transform @@ -390,7 +405,8 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) - see the pyCMS documentation for details on rendering intents and what they do. + see the pyCMS documentation for details on rendering intents and what + they do. :param flags: Integer (0-...) specifying additional flags :returns: A CmsTransform class object. :exception PyCMSError: @@ -400,19 +416,26 @@ def buildTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent raise PyCMSError("renderingIntent must be an integer between 0 and 3") if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): - raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG) + raise PyCMSError( + "flags must be an integer between 0 and %s" + _MAX_FLAG) try: if not isinstance(inputProfile, ImageCmsProfile): inputProfile = ImageCmsProfile(inputProfile) if not isinstance(outputProfile, ImageCmsProfile): outputProfile = ImageCmsProfile(outputProfile) - return ImageCmsTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent, flags=flags) + return ImageCmsTransform( + inputProfile, outputProfile, inMode, outMode, + renderingIntent, flags=flags) except (IOError, TypeError, ValueError) as v: raise PyCMSError(v) -def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMode, renderingIntent=INTENT_PERCEPTUAL, proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC, flags=FLAGS["SOFTPROOFING"]): +def buildProofTransform( + inputProfile, outputProfile, proofProfile, inMode, outMode, + renderingIntent=INTENT_PERCEPTUAL, + proofRenderingIntent=INTENT_ABSOLUTE_COLORIMETRIC, + flags=FLAGS["SOFTPROOFING"]): """ (pyCMS) Builds an ICC transform mapping from the inputProfile to the outputProfile, but tries to simulate the result that would be @@ -451,17 +474,17 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo when the simulated device has a much wider gamut than the output device, you may obtain marginal results. - :param inputProfile: String, as a valid filename path to the ICC input profile - you wish to use for this transform, or a profile object + :param inputProfile: String, as a valid filename path to the ICC input + profile you wish to use for this transform, or a profile object :param outputProfile: String, as a valid filename path to the ICC output (monitor, usually) profile you wish to use for this transform, or a profile object - :param proofProfile: String, as a valid filename path to the ICC proof profile - you wish to use for this transform, or a profile object - :param inMode: String, as a valid PIL mode that the appropriate profile also - supports (i.e. "RGB", "RGBA", "CMYK", etc.) - :param outMode: String, as a valid PIL mode that the appropriate profile also - supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param proofProfile: String, as a valid filename path to the ICC proof + profile you wish to use for this transform, or a profile object + :param inMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) + :param outMode: String, as a valid PIL mode that the appropriate profile + also supports (i.e. "RGB", "RGBA", "CMYK", etc.) :param renderingIntent: Integer (0-3) specifying the rendering intent you wish to use for the input->proof (simulated) transform @@ -470,7 +493,8 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) - see the pyCMS documentation for details on rendering intents and what they do. + see the pyCMS documentation for details on rendering intents and what + they do. :param proofRenderingIntent: Integer (0-3) specifying the rendering intent you wish to use for proof->output transform @@ -479,7 +503,8 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) - see the pyCMS documentation for details on rendering intents and what they do. + see the pyCMS documentation for details on rendering intents and what + they do. :param flags: Integer (0-...) specifying additional flags :returns: A CmsTransform class object. :exception PyCMSError: @@ -489,7 +514,8 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo raise PyCMSError("renderingIntent must be an integer between 0 and 3") if not isinstance(flags, int) or not (0 <= flags <= _MAX_FLAG): - raise PyCMSError("flags must be an integer between 0 and %s" + _MAX_FLAG) + raise PyCMSError( + "flags must be an integer between 0 and %s" + _MAX_FLAG) try: if not isinstance(inputProfile, ImageCmsProfile): @@ -498,7 +524,9 @@ def buildProofTransform(inputProfile, outputProfile, proofProfile, inMode, outMo outputProfile = ImageCmsProfile(outputProfile) if not isinstance(proofProfile, ImageCmsProfile): proofProfile = ImageCmsProfile(proofProfile) - return ImageCmsTransform(inputProfile, outputProfile, inMode, outMode, renderingIntent, proofProfile, proofRenderingIntent, flags) + return ImageCmsTransform( + inputProfile, outputProfile, inMode, outMode, renderingIntent, + proofProfile, proofRenderingIntent, flags) except (IOError, TypeError, ValueError) as v: raise PyCMSError(v) @@ -523,8 +551,8 @@ def applyTransform(im, transform, inPlace=0): is raised. This function applies a pre-calculated transform (from - ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles()) to an - image. The transform can be used for multiple images, saving + ImageCms.buildTransform() or ImageCms.buildTransformFromOpenProfiles()) + to an image. The transform can be used for multiple images, saving considerable calcuation time if doing the same conversion multiple times. If you want to modify im in-place instead of receiving a new image as @@ -537,10 +565,12 @@ def applyTransform(im, transform, inPlace=0): :param im: A PIL Image object, and im.mode must be the same as the inMode supported by the transform. :param transform: A valid CmsTransform class object - :param inPlace: Bool (1 == True, 0 or None == False). If True, im is modified - in place and None is returned, if False, a new Image object with the - transform applied is returned (and im is not changed). The default is False. - :returns: Either None, or a new PIL Image object, depending on the value of inPlace + :param inPlace: Bool (1 == True, 0 or None == False). If True, im is + modified in place and None is returned, if False, a new Image object + with the transform applied is returned (and im is not changed). The + default is False. + :returns: Either None, or a new PIL Image object, depending on the value of + inPlace :exception PyCMSError: """ @@ -572,24 +602,29 @@ def createProfile(colorSpace, colorTemp=-1): ImageCms.buildTransformFromOpenProfiles() to create a transform to apply to images. - :param colorSpace: String, the color space of the profile you wish to create. + :param colorSpace: String, the color space of the profile you wish to + create. Currently only "LAB", "XYZ", and "sRGB" are supported. :param colorTemp: Positive integer for the white point for the profile, in degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50 - illuminant if omitted (5000k). colorTemp is ONLY applied to LAB profiles, - and is ignored for XYZ and sRGB. + illuminant if omitted (5000k). colorTemp is ONLY applied to LAB + profiles, and is ignored for XYZ and sRGB. :returns: A CmsProfile class object :exception PyCMSError: """ if colorSpace not in ["LAB", "XYZ", "sRGB"]: - raise PyCMSError("Color space not supported for on-the-fly profile creation (%s)" % colorSpace) + raise PyCMSError( + "Color space not supported for on-the-fly profile creation (%s)" + % colorSpace) if colorSpace == "LAB": try: colorTemp = float(colorTemp) except: - raise PyCMSError("Color temperature must be numeric, \"%s\" not valid" % colorTemp) + raise PyCMSError( + "Color temperature must be numeric, \"%s\" not valid" + % colorTemp) try: return core.createProfile(colorSpace, colorTemp) @@ -611,10 +646,10 @@ def getProfileName(profile): profile was originally created. Sometimes this tag also contains additional information supplied by the creator. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :returns: A string containing the internal name of the profile as stored in an - ICC tag. + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal name of the profile as stored + in an ICC tag. :exception PyCMSError: """ @@ -653,10 +688,10 @@ def getProfileInfo(profile): info tag. This often contains details about the profile, and how it was created, as supplied by the creator. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :returns: A string containing the internal profile information stored in an ICC - tag. + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. :exception PyCMSError: """ @@ -664,7 +699,8 @@ def getProfileInfo(profile): if not isinstance(profile, ImageCmsProfile): profile = ImageCmsProfile(profile) # add an extra newline to preserve pyCMS compatibility - # Python, not C. the white point bits weren't working well, so skipping. + # Python, not C. the white point bits weren't working well, + # so skipping. # // info was description \r\n\r\n copyright \r\n\r\n K007 tag \r\n\r\n whitepoint description = profile.profile.product_description cpright = profile.profile.product_copyright @@ -691,10 +727,10 @@ def getProfileCopyright(profile): Use this function to obtain the information stored in the profile's copyright tag. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :returns: A string containing the internal profile information stored in an ICC - tag. + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. :exception PyCMSError: """ try: @@ -713,16 +749,16 @@ def getProfileManufacturer(profile): If profile isn't a valid CmsProfile object or filename to a profile, a PyCMSError is raised. - If an error occurs while trying to obtain the manufacturer tag, a PyCMSError - is raised + If an error occurs while trying to obtain the manufacturer tag, a + PyCMSError is raised Use this function to obtain the information stored in the profile's manufacturer tag. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :returns: A string containing the internal profile information stored in an ICC - tag. + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. :exception PyCMSError: """ try: @@ -747,10 +783,10 @@ def getProfileModel(profile): Use this function to obtain the information stored in the profile's model tag. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :returns: A string containing the internal profile information stored in an ICC - tag. + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in + an ICC tag. :exception PyCMSError: """ @@ -776,10 +812,10 @@ def getProfileDescription(profile): Use this function to obtain the information stored in the profile's description tag. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :returns: A string containing the internal profile information stored in an ICC - tag. + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: A string containing the internal profile information stored in an + ICC tag. :exception PyCMSError: """ @@ -808,16 +844,18 @@ def getDefaultIntent(profile): If you wish to use a different intent than returned, use ImageCms.isIntentSupported() to verify it will work first. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :returns: Integer 0-3 specifying the default rendering intent for this profile. + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :returns: Integer 0-3 specifying the default rendering intent for this + profile. INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) - see the pyCMS documentation for details on rendering intents and what they do. + see the pyCMS documentation for details on rendering intents and what + they do. :exception PyCMSError: """ @@ -844,17 +882,18 @@ def isIntentSupported(profile, intent, direction): potential PyCMSError that will occur if they don't support the modes you select. - :param profile: EITHER a valid CmsProfile object, OR a string of the filename - of an ICC profile. - :param intent: Integer (0-3) specifying the rendering intent you wish to use - with this profile + :param profile: EITHER a valid CmsProfile object, OR a string of the + filename of an ICC profile. + :param intent: Integer (0-3) specifying the rendering intent you wish to + use with this profile INTENT_PERCEPTUAL = 0 (DEFAULT) (ImageCms.INTENT_PERCEPTUAL) INTENT_RELATIVE_COLORIMETRIC = 1 (ImageCms.INTENT_RELATIVE_COLORIMETRIC) INTENT_SATURATION = 2 (ImageCms.INTENT_SATURATION) INTENT_ABSOLUTE_COLORIMETRIC = 3 (ImageCms.INTENT_ABSOLUTE_COLORIMETRIC) - see the pyCMS documentation for details on rendering intents and what they do. + see the pyCMS documentation for details on rendering intents and what + they do. :param direction: Integer specifing if the profile is to be used for input, output, or proof From e9830572947243d7d10d786b304804fee43b059d Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 2 Jun 2014 12:41:48 +0300 Subject: [PATCH 23/33] pep8/pyflakes --- Tests/test_imagecms.py | 84 +++++++++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 34 deletions(-) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index dcb445c9f..8a7c172b2 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -9,12 +9,13 @@ except ImportError: SRGB = "Tests/icc/sRGB.icm" + def test_sanity(): # basic smoke test. # this mostly follows the cms_test outline. - v = ImageCms.versions() # should return four strings + v = ImageCms.versions() # should return four strings assert_equal(v[0], '1.0.0 pil') assert_equal(list(map(type, v)), [str, str, str, str]) @@ -41,12 +42,15 @@ def test_sanity(): assert_image(i, "RGB", (128, 128)) # test PointTransform convenience API - im = lena().point(t) + lena().point(t) + def test_name(): # get profile information for file assert_equal(ImageCms.getProfileName(SRGB).strip(), 'IEC 61966-2.1 Default RGB colour space - sRGB') + + def x_test_info(): assert_equal(ImageCms.getProfileInfo(SRGB).splitlines(), ['sRGB IEC61966-2.1', '', @@ -54,11 +58,13 @@ def x_test_info(): 'WhitePoint : D65 (daylight)', '', 'Tests/icc/sRGB.icm']) + def test_intent(): assert_equal(ImageCms.getDefaultIntent(SRGB), 0) assert_equal(ImageCms.isIntentSupported( - SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, - ImageCms.DIRECTION_INPUT), 1) + SRGB, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, + ImageCms.DIRECTION_INPUT), 1) + def test_profile_object(): # same, using profile object @@ -69,8 +75,9 @@ def test_profile_object(): # ['sRGB built-in', '', 'WhitePoint : D65 (daylight)', '', '']) assert_equal(ImageCms.getDefaultIntent(p), 0) assert_equal(ImageCms.isIntentSupported( - p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, - ImageCms.DIRECTION_INPUT), 1) + p, ImageCms.INTENT_ABSOLUTE_COLORIMETRIC, + ImageCms.DIRECTION_INPUT), 1) + def test_extensions(): # extensions @@ -79,12 +86,21 @@ def test_extensions(): assert_equal(ImageCms.getProfileName(p).strip(), 'IEC 61966-2.1 Default RGB colour space - sRGB') + def test_exceptions(): # the procedural pyCMS API uses PyCMSError for all sorts of errors - assert_exception(ImageCms.PyCMSError, lambda: ImageCms.profileToProfile(lena(), "foo", "bar")) - assert_exception(ImageCms.PyCMSError, lambda: ImageCms.buildTransform("foo", "bar", "RGB", "RGB")) - assert_exception(ImageCms.PyCMSError, lambda: ImageCms.getProfileName(None)) - assert_exception(ImageCms.PyCMSError, lambda: ImageCms.isIntentSupported(SRGB, None, None)) + assert_exception( + ImageCms.PyCMSError, + lambda: ImageCms.profileToProfile(lena(), "foo", "bar")) + assert_exception( + ImageCms.PyCMSError, + lambda: ImageCms.buildTransform("foo", "bar", "RGB", "RGB")) + assert_exception( + ImageCms.PyCMSError, + lambda: ImageCms.getProfileName(None)) + assert_exception( + ImageCms.PyCMSError, + lambda: ImageCms.isIntentSupported(SRGB, None, None)) def test_display_profile(): @@ -93,61 +109,63 @@ def test_display_profile(): def test_lab_color_profile(): - pLab = ImageCms.createProfile("LAB", 5000) - pLab = ImageCms.createProfile("LAB", 6500) + ImageCms.createProfile("LAB", 5000) + ImageCms.createProfile("LAB", 6500) + def test_simple_lab(): - i = Image.new('RGB', (10,10), (128,128,128)) + i = Image.new('RGB', (10, 10), (128, 128, 128)) - pLab = ImageCms.createProfile("LAB") + pLab = ImageCms.createProfile("LAB") t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") i_lab = ImageCms.applyTransform(i, t) - assert_equal(i_lab.mode, 'LAB') - k = i_lab.getpixel((0,0)) - assert_equal(k, (137,128,128)) # not a linear luminance map. so L != 128 + k = i_lab.getpixel((0, 0)) + assert_equal(k, (137, 128, 128)) # not a linear luminance map. so L != 128 - L = i_lab.getdata(0) + L = i_lab.getdata(0) a = i_lab.getdata(1) b = i_lab.getdata(2) - assert_equal(list(L), [137]*100) - assert_equal(list(a), [128]*100) - assert_equal(list(b), [128]*100) + assert_equal(list(L), [137] * 100) + assert_equal(list(a), [128] * 100) + assert_equal(list(b), [128] * 100) + - def test_lab_color(): - pLab = ImageCms.createProfile("LAB") + pLab = ImageCms.createProfile("LAB") t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") - # need to add a type mapping for some PIL type to TYPE_Lab_8 in findLCMSType, - # and have that mapping work back to a PIL mode. (likely RGB) + # Need to add a type mapping for some PIL type to TYPE_Lab_8 in + # findLCMSType, and have that mapping work back to a PIL mode (likely RGB). i = ImageCms.applyTransform(lena(), t) assert_image(i, "LAB", (128, 128)) - - # i.save('temp.lab.tif') # visually verified vs PS. + + # i.save('temp.lab.tif') # visually verified vs PS. target = Image.open('Tests/images/lena.Lab.tif') assert_image_similar(i, target, 30) + def test_lab_srgb(): - pLab = ImageCms.createProfile("LAB") + pLab = ImageCms.createProfile("LAB") t = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") img = Image.open('Tests/images/lena.Lab.tif') img_srgb = ImageCms.applyTransform(img, t) - # img_srgb.save('temp.srgb.tif') # visually verified vs ps. - + # img_srgb.save('temp.srgb.tif') # visually verified vs ps. + assert_image_similar(lena(), img_srgb, 30) + def test_lab_roundtrip(): - # check to see if we're at least internally consistent. - pLab = ImageCms.createProfile("LAB") + # check to see if we're at least internally consistent. + pLab = ImageCms.createProfile("LAB") t = ImageCms.buildTransform(SRGB, pLab, "RGB", "LAB") t2 = ImageCms.buildTransform(pLab, SRGB, "LAB", "RGB") @@ -156,5 +174,3 @@ def test_lab_roundtrip(): out = ImageCms.applyTransform(i, t2) assert_image_similar(lena(), out, 2) - - From 9e984a19b11e7e7ba7826550eccff4c5d97be576 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 2 Jun 2014 13:00:21 +0300 Subject: [PATCH 24/33] Add more tests for ImageCms --- Tests/test_imagecms.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 8a7c172b2..5b3859c60 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -25,6 +25,10 @@ def test_sanity(): i = ImageCms.profileToProfile(lena(), SRGB, SRGB) assert_image(i, "RGB", (128, 128)) + i = lena() + ImageCms.profileToProfile(i, SRGB, SRGB, inPlace=True) + assert_image(i, "RGB", (128, 128)) + t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") i = ImageCms.applyTransform(lena(), t) assert_image(i, "RGB", (128, 128)) @@ -51,12 +55,25 @@ def test_name(): 'IEC 61966-2.1 Default RGB colour space - sRGB') -def x_test_info(): +def test_info(): assert_equal(ImageCms.getProfileInfo(SRGB).splitlines(), ['sRGB IEC61966-2.1', '', - 'Copyright (c) 1998 Hewlett-Packard Company', '', - 'WhitePoint : D65 (daylight)', '', - 'Tests/icc/sRGB.icm']) + 'Copyright (c) 1998 Hewlett-Packard Company', '']) + + +def test_copyright(): + assert_equal(ImageCms.getProfileCopyright(SRGB).strip(), + 'Copyright (c) 1998 Hewlett-Packard Company') + + +def test_manufacturer(): + assert_equal(ImageCms.getProfileManufacturer(SRGB).strip(), + 'IEC http://www.iec.ch') + + +def test_description(): + assert_equal(ImageCms.getProfileDescription(SRGB).strip(), + 'sRGB IEC61966-2.1') def test_intent(): From 6115d348b983118bd43fb7c54e8d0a25d2c6fca2 Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 2 Jun 2014 13:19:01 +0300 Subject: [PATCH 25/33] Add more tests for ImageCms --- Tests/test_imagecms.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 5b3859c60..f52101eb1 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -33,6 +33,11 @@ def test_sanity(): i = ImageCms.applyTransform(lena(), t) assert_image(i, "RGB", (128, 128)) + i = lena() + t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") + ImageCms.applyTransform(lena(), t, inPlace=True) + assert_image(i, "RGB", (128, 128)) + p = ImageCms.createProfile("sRGB") o = ImageCms.getOpenProfile(SRGB) t = ImageCms.buildTransformFromOpenProfiles(p, o, "RGB", "RGB") @@ -71,6 +76,11 @@ def test_manufacturer(): 'IEC http://www.iec.ch') +def test_model(): + assert_equal(ImageCms.getProfileModel(SRGB).strip(), + 'IEC 61966-2.1 Default RGB colour space - sRGB') + + def test_description(): assert_equal(ImageCms.getProfileDescription(SRGB).strip(), 'sRGB IEC61966-2.1') From beedca78e3c33e5e4c50d2a7389e6ea6762a0dea Mon Sep 17 00:00:00 2001 From: hugovk Date: Mon, 2 Jun 2014 15:57:50 +0300 Subject: [PATCH 26/33] Show counts of pep8/pyflakes after build --- .travis.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 36dae5b7a..1d313a088 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ after_success: - coverage report - coveralls - pip install pep8 pyflakes - - pep8 PIL/*.py - - pyflakes PIL/*.py - - pep8 Tests/*.py - - pyflakes Tests/*.py + - pep8 --statistics --count PIL/*.py + - pep8 --statistics --count Tests/*.py + - pyflakes PIL/*.py | tee >(wc -l) + - pyflakes Tests/*.py | tee >(wc -l) From e944297e80b93aae86769830e4dc9f80a6bde66a Mon Sep 17 00:00:00 2001 From: joaoxsouls Date: Sun, 1 Jun 2014 23:57:25 +0100 Subject: [PATCH 27/33] add FreeBSD10 support to doc --- docs/installation.rst | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 94054df82..76c7d4ef5 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -67,11 +67,11 @@ Many of Pillow's features require external libraries: * Pillow has been tested with version **0.1.3**, which does not read transparent webp files. Versions **0.3.0** and **0.4.0** support - transparency. + transparency. -* **tcl/tk** provides support for tkinter bitmap and photo images. +* **tcl/tk** provides support for tkinter bitmap and photo images. -* **openjpeg** provides JPEG 2000 functionality. +* **openjpeg** provides JPEG 2000 functionality. * Pillow has been tested with openjpeg **2.0.0**. @@ -108,7 +108,7 @@ Or for Python 3:: $ sudo apt-get install python3-dev python3-setuptools In Fedora, the command is:: - + $ sudo yum install python-devel Prerequisites are installed on **Ubuntu 10.04 LTS** with:: @@ -185,6 +185,25 @@ to a specific version: $ pip install --use-wheel Pillow==2.3.0 +FreeBSD installation +--------------------- + +.. Note:: Only FreeBSD 10 tested + + +Make sure you have Python's development libraries installed.:: + + $ sudo pkg install python2 + +Or for Python 3:: + + $ sudo pkg install python3 + +Prerequisites are installed on **FreeBSD 10** with:: + + $ sudo pkg install jpeg tiff webp lcms2 freetype2 + + Platform support ---------------- @@ -199,7 +218,7 @@ current versions of Linux, OS X, and Windows. Contributors please test on your platform, edit this document, and send a pull request. -+----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ ++----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ |**Operating system** |**Supported**|**Tested Python versions** |**Tested Pillow versions** |**Tested processors** | +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ | Mac OS X 10.8 Mountain Lion |Yes | 2.6,2.7,3.2,3.3 | |x86-64 | @@ -224,6 +243,8 @@ current versions of Linux, OS X, and Windows. +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ | Gentoo Linux |Yes | 2.7,3.2 | 2.1.0 |x86-64 | +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ +| FreeBSD 10 |Yes | 2.7,3.4 | 2.4,2.3.1 |x86-64 | ++----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ | Windows 7 Pro |Yes | 2.7,3.2,3.3 | 2.2.1 |x86-64 | +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ | Windows Server 2008 R2 Enterprise|Yes | 3.3 | |x86-64 | @@ -232,4 +253,3 @@ current versions of Linux, OS X, and Windows. +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ | Windows 8.1 Pro |Yes | 2.6,2.7,3.2,3.3,3.4 | 2.3.0, 2.4.0 |x86,x86-64 | +----------------------------------+-------------+------------------------------+------------------------------+-----------------------+ - From 9dc2346dead9853edec57514cdd99ccf4898d0e8 Mon Sep 17 00:00:00 2001 From: Hugo Date: Tue, 3 Jun 2014 11:06:27 +0300 Subject: [PATCH 28/33] Fix Travis CI badge for new python-pillow name --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 277c5d01f..33b9813d0 100644 --- a/README.rst +++ b/README.rst @@ -5,8 +5,8 @@ Pillow Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. -.. image:: https://travis-ci.org/python-imaging/Pillow.svg?branch=master - :target: https://travis-ci.org/python-imaging/Pillow +.. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master + :target: https://travis-ci.org/python-pillow/Pillow :alt: Travis CI build status .. image:: https://pypip.in/v/Pillow/badge.png From c9a4272af62f96974a1651c0fe877337aec79bc9 Mon Sep 17 00:00:00 2001 From: hugovk Date: Tue, 3 Jun 2014 13:02:44 +0300 Subject: [PATCH 29/33] Replace python-imaging with python-pillow (but yet not Coveralls) --- CHANGES.rst | 38 ++++++++++++++++---------------- PIL/TiffImagePlugin.py | 20 ++++++++--------- Tests/test_file_gif.py | 16 +++++++------- Tests/test_file_jpeg.py | 6 ++--- Tests/test_file_libtiff.py | 38 ++++++++++++++++---------------- Tests/test_file_png.py | 10 ++++----- Tests/test_file_tiff_metadata.py | 16 +++++++------- Tests/test_image_convert.py | 4 ++-- Tests/test_image_getpixel.py | 10 ++++----- Tests/test_image_point.py | 4 ++-- Tests/test_image_transform.py | 38 ++++++++++++++++---------------- Tests/test_imagefile.py | 6 ++--- Tests/test_locale.py | 4 ++-- Tests/test_numpy.py | 14 ++++++------ docs/_templates/sidebarhelp.html | 2 +- docs/about.rst | 6 ++--- docs/index.rst | 8 +++---- setup.py | 8 +++---- 18 files changed, 124 insertions(+), 124 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 6b7d0162f..fbe2d3b0c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,34 +8,34 @@ Changelog (Pillow) - Added tests for Spider files [hugovk] - + - Use libtiff to write any compressed tiff files [wiredfool] - + - Support for pickling Image objects [hugovk] - Fixed resolution handling for EPS thumbnails [eliempje] - + - Fixed rendering of some binary EPS files (Issue #302) - [eliempje] - + [eliempje] + - Rename variables not to use built-in function names [hugovk] - -- Ignore junk JPEG markers + +- Ignore junk JPEG markers [hugovk] - + - Change default interpolation for Image.thumbnail to Image.ANTIALIAS [hugovk] - + - Add tests and fixes for saving PDFs [hugovk] - + - Remove transparency resource after P->RGBA conversion [hugovk] - + - Clean up preprocessor cruft for Windows [CounterPillow] @@ -45,13 +45,13 @@ Changelog (Pillow) - Added Image.close, context manager support. [wiredfool] -- Added support for 16 bit PGM files. +- Added support for 16 bit PGM files. [wiredfool] - Updated OleFileIO to version 0.30 from upstream [hugovk] -- Added support for additional TIFF floating point format +- Added support for additional TIFF floating point format [Hijackal] - Have the tempfile use a suffix with a dot @@ -81,7 +81,7 @@ Changelog (Pillow) - Added support for JPEG 2000 [al45tair] -- Add more detailed error messages to Image.py +- Add more detailed error messages to Image.py [larsmans] - Avoid conflicting _expand functions in PIL & MINGW, fixes #538 @@ -109,7 +109,7 @@ Changelog (Pillow) [wiredfool] - Fixed palette handling when converting from mode P->RGB->P - [d_schmidt] + [d_schmidt] - Fixed saving mode P image as a PNG with transparency = palette color 0 [d-schmidt] @@ -119,7 +119,7 @@ Changelog (Pillow) - Fixed DOS with invalid palette size or invalid image size in BMP file [wiredfool] - + - Added support for BMP version 4 and 5 [eddwardo, wiredfool] @@ -152,7 +152,7 @@ Changelog (Pillow) - Prefer homebrew freetype over X11 freetype (but still allow both) [dmckeone] - + 2.3.1 (2014-03-14) ------------------ @@ -277,7 +277,7 @@ Changelog (Pillow) [nikmolnar] - Fix for encoding of b_whitespace, similar to closed issue #272 - [mhogg] + [mhogg] - Fix #273: Add numpy array interface support for 16 and 32 bit integer modes [cgohlke] @@ -437,7 +437,7 @@ Changelog (Pillow) - Add Python 3 support. (Pillow >= 2.0.0 supports Python 2.6, 2.7, 3.2, 3.3. Pillow < 2.0.0 supports Python 2.4, 2.5, 2.6, 2.7.) [fluggo] -- Add PyPy support (experimental, please see: https://github.com/python-imaging/Pillow/issues/67) +- Add PyPy support (experimental, please see: https://github.com/python-pillow/Pillow/issues/67) - Add WebP support. [lqs] diff --git a/PIL/TiffImagePlugin.py b/PIL/TiffImagePlugin.py index 11b92747c..34cb020aa 100644 --- a/PIL/TiffImagePlugin.py +++ b/PIL/TiffImagePlugin.py @@ -54,7 +54,7 @@ import collections import itertools import os -# Set these to true to force use of libtiff for reading or writing. +# Set these to true to force use of libtiff for reading or writing. READ_LIBTIFF = False WRITE_LIBTIFF= False @@ -238,7 +238,7 @@ class ImageFileDirectory(collections.MutableMapping): Value: integer corresponding to the data type from `TiffTags.TYPES` - 'internal' + 'internal' * self.tags = {} Key: numerical tiff tag number Value: Decoded data, Generally a tuple. * If set from __setval__ -- always a tuple @@ -489,10 +489,10 @@ class ImageFileDirectory(collections.MutableMapping): if tag in self.tagtype: typ = self.tagtype[tag] - + if Image.DEBUG: print ("Tag %s, Type: %s, Value: %s" % (tag, typ, value)) - + if typ == 1: # byte data if isinstance(value, tuple): @@ -512,7 +512,7 @@ class ImageFileDirectory(collections.MutableMapping): # and doesn't match the tiff spec: 8-bit byte that # contains a 7-bit ASCII code; the last byte must be # NUL (binary zero). Also, I don't think this was well - # excersized before. + # excersized before. data = value = b"" + value.encode('ascii', 'replace') + b"\0" else: # integer data @@ -859,7 +859,7 @@ class TiffImageFile(ImageFile.ImageFile): # libtiff handles the fillmode for us, so 1;IR should # actually be 1;I. Including the R double reverses the # bits, so stripes of the image are reversed. See - # https://github.com/python-imaging/Pillow/issues/279 + # https://github.com/python-pillow/Pillow/issues/279 if fillorder == 2: key = ( self.tag.prefix, photo, format, 1, @@ -984,11 +984,11 @@ def _save(im, fp, filename): compression = im.encoderinfo.get('compression',im.info.get('compression','raw')) - libtiff = WRITE_LIBTIFF or compression != 'raw' + libtiff = WRITE_LIBTIFF or compression != 'raw' # required for color libtiff images ifd[PLANAR_CONFIGURATION] = getattr(im, '_planar_configuration', 1) - + # -- multi-page -- skip TIFF header on subsequent pages if not libtiff and fp.tell() == 0: # tiff header (write via IFD to get everything right) @@ -1025,7 +1025,7 @@ def _save(im, fp, filename): # which support profiles as TIFF) -- 2008-06-06 Florian Hoech if "icc_profile" in im.info: ifd[ICCPROFILE] = im.info["icc_profile"] - + if "description" in im.encoderinfo: ifd[IMAGEDESCRIPTION] = im.encoderinfo["description"] if "resolution" in im.encoderinfo: @@ -1091,7 +1091,7 @@ def _save(im, fp, filename): blocklist = [STRIPOFFSETS, STRIPBYTECOUNTS, ROWSPERSTRIP, ICCPROFILE] # ICC Profile crashes. atts={} - # bits per sample is a single short in the tiff directory, not a list. + # bits per sample is a single short in the tiff directory, not a list. atts[BITSPERSAMPLE] = bits[0] # Merge the ones that we have with (optional) more bits from # the original file, e.g x,y resolution so that we can diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 4318e178e..566baa200 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -37,7 +37,7 @@ def test_roundtrip(): assert_image_similar(reread.convert('RGB'), im, 50) def test_roundtrip2(): - #see https://github.com/python-imaging/Pillow/issues/403 + #see https://github.com/python-pillow/Pillow/issues/403 out = tempfile('temp.gif') im = Image.open('Images/lena.gif') im2 = im.copy() @@ -48,11 +48,11 @@ def test_roundtrip2(): def test_palette_handling(): - # see https://github.com/python-imaging/Pillow/issues/513 + # see https://github.com/python-pillow/Pillow/issues/513 im = Image.open('Images/lena.gif') im = im.convert('RGB') - + im = im.resize((100,100), Image.ANTIALIAS) im2 = im.convert('P', palette=Image.ADAPTIVE, colors=256) @@ -60,11 +60,11 @@ def test_palette_handling(): im2.save(f, optimize=True) reloaded = Image.open(f) - + assert_image_similar(im, reloaded.convert('RGB'), 10) - + def test_palette_434(): - # see https://github.com/python-imaging/Pillow/issues/434 + # see https://github.com/python-pillow/Pillow/issues/434 def roundtrip(im, *args, **kwargs): out = tempfile('temp.gif') @@ -78,10 +78,10 @@ def test_palette_434(): assert_image_equal(*roundtrip(im)) assert_image_equal(*roundtrip(im, optimize=True)) - + im = im.convert("RGB") # check automatic P conversion reloaded = roundtrip(im)[1].convert('RGB') assert_image_equal(im, reloaded) - + diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 4871c3fbf..43ed55969 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -126,7 +126,7 @@ def test_optimize(): def test_optimize_large_buffer(): - # https://github.com/python-imaging/Pillow/issues/148 + # https://github.com/python-pillow/Pillow/issues/148 f = tempfile('temp.jpg') # this requires ~ 1.5x Image.MAXBLOCK im = Image.new("RGB", (4096, 4096), 0xff3333) @@ -159,7 +159,7 @@ def test_progressive_large_buffer_highest_quality(): def test_large_exif(): - # https://github.com/python-imaging/Pillow/issues/148 + # https://github.com/python-pillow/Pillow/issues/148 f = tempfile('temp.jpg') im = lena() im.save(f, 'JPEG', quality=90, exif=b"1"*65532) @@ -231,7 +231,7 @@ def test_quality_keep(): def test_junk_jpeg_header(): - # https://github.com/python-imaging/Pillow/issues/630 + # https://github.com/python-pillow/Pillow/issues/630 filename = "Tests/images/junk_jpeg_header.jpg" assert_no_exception(lambda: Image.open(filename)) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 58fa75239..fddff443a 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -71,7 +71,7 @@ def test_g4_eq_png(): assert_image_equal(g4, png) -# see https://github.com/python-imaging/Pillow/issues/279 +# see https://github.com/python-pillow/Pillow/issues/279 def test_g4_fillorder_eq_png(): """ Checking that we're actually getting the data that we expect""" png = Image.open('Tests/images/g4-fillorder-test.png') @@ -96,7 +96,7 @@ def test_g4_write(): assert_equal(reread.info['compression'], 'group4') assert_equal(reread.info['compression'], orig.info['compression']) - + assert_false(orig.tobytes() == reread.tobytes()) def test_adobe_deflate_tiff(): @@ -120,7 +120,7 @@ def test_write_metadata(): original = img.tag.named() reloaded = loaded.tag.named() - # PhotometricInterpretation is set from SAVE_INFO, not the original image. + # PhotometricInterpretation is set from SAVE_INFO, not the original image. ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber', 'PhotometricInterpretation'] for tag, value in reloaded.items(): @@ -133,7 +133,7 @@ def test_write_metadata(): assert_equal(original[tag], value, "%s didn't roundtrip" % tag) for tag, value in original.items(): - if tag not in ignored: + if tag not in ignored: if tag.endswith('Resolution'): val = reloaded[tag] assert_almost_equal(val[0][0]/val[0][1], value[0][0]/value[0][1], @@ -164,7 +164,7 @@ def test_little_endian(): else: assert_equal(b[0], b'\xe0') assert_equal(b[1], b'\x01') - + out = tempfile("temp.tif") #out = "temp.le.tif" @@ -174,8 +174,8 @@ def test_little_endian(): assert_equal(reread.info['compression'], im.info['compression']) assert_equal(reread.getpixel((0,0)), 480) # UNDONE - libtiff defaults to writing in native endian, so - # on big endian, we'll get back mode = 'I;16B' here. - + # on big endian, we'll get back mode = 'I;16B' here. + def test_big_endian(): im = Image.open('Tests/images/16bit.MM.deflate.tif') @@ -191,7 +191,7 @@ def test_big_endian(): else: assert_equal(b[0], b'\x01') assert_equal(b[1], b'\xe0') - + out = tempfile("temp.tif") im.save(out) reread = Image.open(out) @@ -203,12 +203,12 @@ def test_g4_string_info(): """Tests String data in info directory""" file = "Tests/images/lena_g4_500.tif" orig = Image.open(file) - + out = tempfile("temp.tif") orig.tag[269] = 'temp.tif' orig.save(out) - + reread = Image.open(out) assert_equal('temp.tif', reread.tag[269]) @@ -223,8 +223,8 @@ def test_12bit_rawmode(): # convert 12bit.cropped.tif -depth 16 tmp.tif # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0, - # so we need to unshift so that the integer values are the same. - + # so we need to unshift so that the integer values are the same. + im2 = Image.open('Tests/images/12in16bit.tif') if Image.DEBUG: @@ -235,11 +235,11 @@ def test_12bit_rawmode(): print (im2.getpixel((0,0))) print (im2.getpixel((0,1))) print (im2.getpixel((0,2))) - + assert_image_equal(im, im2) def test_blur(): - # test case from irc, how to do blur on b/w image and save to compressed tif. + # test case from irc, how to do blur on b/w image and save to compressed tif. from PIL import ImageFilter out = tempfile('temp.tif') im = Image.open('Tests/images/pport_g4.tif') @@ -266,7 +266,7 @@ def test_compressions(): im.save(out, compression='jpeg') im2 = Image.open(out) assert_image_similar(im, im2, 30) - + def test_cmyk_save(): im = lena('CMYK') @@ -280,7 +280,7 @@ def xtest_bw_compression_wRGB(): """ This test passes, but when running all tests causes a failure due to output on stderr from the error thrown by libtiff. We need to capture that but not now""" - + im = lena('RGB') out = tempfile('temp.tif') @@ -293,8 +293,8 @@ def test_fp_leak(): fn = im.fp.fileno() assert_no_exception(lambda: os.fstat(fn)) - im.load() # this should close it. - assert_exception(OSError, lambda: os.fstat(fn)) + im.load() # this should close it. + assert_exception(OSError, lambda: os.fstat(fn)) im = None # this should force even more closed. - assert_exception(OSError, lambda: os.fstat(fn)) + assert_exception(OSError, lambda: os.fstat(fn)) assert_exception(OSError, lambda: os.close(fn)) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index c17829194..0b0ad3ca7 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -101,7 +101,7 @@ def test_bad_text(): assert_equal(im.info, {'spam': 'egg\x00'}) def test_bad_ztxt(): - # Test reading malformed zTXt chunks (python-imaging/Pillow#318) + # Test reading malformed zTXt chunks (python-pillow/Pillow#318) im = load(HEAD + chunk(b'zTXt') + TAIL) assert_equal(im.info, {}) @@ -182,7 +182,7 @@ def test_save_l_transparency(): file = tempfile("temp.png") assert_no_exception(lambda: im.save(file)) - # There are 559 transparent pixels. + # There are 559 transparent pixels. im = im.convert('RGBA') assert_equal(im.split()[3].getcolors()[0][0], 559) @@ -255,7 +255,7 @@ def test_trns_p(): # Check writing a transparency of 0, issue #528 im = lena('P') im.info['transparency']=0 - + f = tempfile("temp.png") im.save(f) @@ -263,8 +263,8 @@ def test_trns_p(): assert_true('transparency' in im2.info) assert_image_equal(im2.convert('RGBA'), im.convert('RGBA')) - - + + def test_save_icc_profile_none(): # check saving files with an ICC profile set to None (omit profile) in_file = "Tests/images/icc_profile_none.png" diff --git a/Tests/test_file_tiff_metadata.py b/Tests/test_file_tiff_metadata.py index 354eb972f..c759d8c0d 100644 --- a/Tests/test_file_tiff_metadata.py +++ b/Tests/test_file_tiff_metadata.py @@ -6,9 +6,9 @@ tag_ids = dict(zip(TiffTags.TAGS.values(), TiffTags.TAGS.keys())) def test_rt_metadata(): """ Test writing arbitray metadata into the tiff image directory Use case is ImageJ private tags, one numeric, one arbitrary - data. https://github.com/python-imaging/Pillow/issues/291 + data. https://github.com/python-pillow/Pillow/issues/291 """ - + img = lena() textdata = "This is some arbitrary metadata for a text field" @@ -20,15 +20,15 @@ def test_rt_metadata(): f = tempfile("temp.tif") img.save(f, tiffinfo=info) - + loaded = Image.open(f) assert_equal(loaded.tag[50838], (len(textdata),)) assert_equal(loaded.tag[50839], textdata) - + def test_read_metadata(): img = Image.open('Tests/images/lena_g4.tif') - + known = {'YResolution': ((1207959552, 16777216),), 'PlanarConfiguration': (1,), 'BitsPerSample': (1,), @@ -48,7 +48,7 @@ def test_read_metadata(): 'StripOffsets': (8,), 'Software': 'ImageMagick 6.5.7-8 2012-08-17 Q16 http://www.imagemagick.org'} - # assert_equal is equivalent, but less helpful in telling what's wrong. + # assert_equal is equivalent, but less helpful in telling what's wrong. named = img.tag.named() for tag, value in named.items(): assert_equal(known[tag], value) @@ -70,11 +70,11 @@ def test_write_metadata(): reloaded = loaded.tag.named() ignored = ['StripByteCounts', 'RowsPerStrip', 'PageNumber', 'StripOffsets'] - + for tag, value in reloaded.items(): if tag not in ignored: assert_equal(original[tag], value, "%s didn't roundtrip" % tag) for tag, value in original.items(): - if tag not in ignored: + if tag not in ignored: assert_equal(value, reloaded[tag], "%s didn't roundtrip" % tag) diff --git a/Tests/test_image_convert.py b/Tests/test_image_convert.py index 6a39b0e3b..ce7412e94 100644 --- a/Tests/test_image_convert.py +++ b/Tests/test_image_convert.py @@ -28,7 +28,7 @@ def test_default(): assert_image(im, "RGB", im.size) -# ref https://github.com/python-imaging/Pillow/issues/274 +# ref https://github.com/python-pillow/Pillow/issues/274 def _test_float_conversion(im): orig = im.getpixel((5, 5)) @@ -76,7 +76,7 @@ def test_trns_p(): assert_no_exception(lambda: rgb.save(f)) -# ref https://github.com/python-imaging/Pillow/issues/664 +# ref https://github.com/python-pillow/Pillow/issues/664 def test_trns_p_rgba(): # Arrange diff --git a/Tests/test_image_getpixel.py b/Tests/test_image_getpixel.py index 67f5904a2..da3d8115e 100644 --- a/Tests/test_image_getpixel.py +++ b/Tests/test_image_getpixel.py @@ -16,27 +16,27 @@ def color(mode): def check(mode, c=None): if not c: c = color(mode) - + #check putpixel im = Image.new(mode, (1, 1), None) im.putpixel((0, 0), c) assert_equal(im.getpixel((0, 0)), c, "put/getpixel roundtrip failed for mode %s, color %s" % (mode, c)) - + # check inital color im = Image.new(mode, (1, 1), c) assert_equal(im.getpixel((0, 0)), c, "initial color failed for mode %s, color %s " % (mode, color)) -def test_basic(): +def test_basic(): for mode in ("1", "L", "LA", "I", "I;16", "I;16B", "F", "P", "PA", "RGB", "RGBA", "RGBX", "CMYK","YCbCr"): check(mode) def test_signedness(): - # see https://github.com/python-imaging/Pillow/issues/452 + # see https://github.com/python-pillow/Pillow/issues/452 # pixelaccess is using signed int* instead of uint* for mode in ("I;16", "I;16B"): check(mode, 2**15-1) @@ -44,6 +44,6 @@ def test_signedness(): check(mode, 2**15+1) check(mode, 2**16-1) - + diff --git a/Tests/test_image_point.py b/Tests/test_image_point.py index 34233f80e..4fc11ca05 100644 --- a/Tests/test_image_point.py +++ b/Tests/test_image_point.py @@ -4,7 +4,7 @@ from PIL import Image if hasattr(sys, 'pypy_version_info'): # This takes _forever_ on pypy. Open Bug, - # see https://github.com/python-imaging/Pillow/issues/484 + # see https://github.com/python-pillow/Pillow/issues/484 skip() def test_sanity(): @@ -26,7 +26,7 @@ def test_sanity(): def test_16bit_lut(): """ Tests for 16 bit -> 8 bit lut for converting I->L images - see https://github.com/python-imaging/Pillow/issues/440 + see https://github.com/python-pillow/Pillow/issues/440 """ im = lena("I") diff --git a/Tests/test_image_transform.py b/Tests/test_image_transform.py index fdee6072f..dd9b6fe5c 100644 --- a/Tests/test_image_transform.py +++ b/Tests/test_image_transform.py @@ -10,9 +10,9 @@ def test_extent(): w//2,h//2), # ul -> lr Image.BILINEAR) - + scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0,0,w,h)) - + assert_image_similar(transformed, scaled, 10) # undone -- precision? def test_quad(): @@ -23,9 +23,9 @@ def test_quad(): (0,0,0,h//2, w//2,h//2,w//2,0), # ul -> ccw around quad Image.BILINEAR) - + scaled = im.resize((w*2, h*2), Image.BILINEAR).crop((0,0,w,h)) - + assert_image_equal(transformed, scaled) def test_mesh(): @@ -48,8 +48,8 @@ def test_mesh(): checker = Image.new('RGBA', im.size) checker.paste(scaled, (0,0)) checker.paste(scaled, (w//2,h//2)) - - assert_image_equal(transformed, checker) + + assert_image_equal(transformed, checker) # now, check to see that the extra area is (0,0,0,0) blank = Image.new('RGBA', (w//2,h//2), (0,0,0,0)) @@ -59,34 +59,34 @@ def test_mesh(): def _test_alpha_premult(op): # create image with half white, half black, with the black half transparent. - # do op, + # do op, # there should be no darkness in the white section. im = Image.new('RGBA', (10,10), (0,0,0,0)); im2 = Image.new('RGBA', (5,10), (255,255,255,255)); im.paste(im2, (0,0)) - + im = op(im, (40,10)) im_background = Image.new('RGB', (40,10), (255,255,255)) im_background.paste(im, (0,0), im) - + hist = im_background.histogram() assert_equal(40*10, hist[-1]) - + def test_alpha_premult_resize(): def op (im, sz): return im.resize(sz, Image.LINEAR) - + _test_alpha_premult(op) - + def test_alpha_premult_transform(): - + def op(im, sz): (w,h) = im.size return im.transform(sz, Image.EXTENT, (0,0, - w,h), + w,h), Image.BILINEAR) _test_alpha_premult(op) @@ -94,7 +94,7 @@ def test_alpha_premult_transform(): def test_blank_fill(): # attempting to hit - # https://github.com/python-imaging/Pillow/issues/254 reported + # https://github.com/python-pillow/Pillow/issues/254 reported # # issue is that transforms with transparent overflow area # contained junk from previous images, especially on systems with @@ -106,11 +106,11 @@ def test_blank_fill(): # # Running by default, but I'd totally understand not doing it in # the future - - foo = [Image.new('RGBA',(1024,1024), (a,a,a,a)) - for a in range(1,65)] - # Yeah. Watch some JIT optimize this out. + foo = [Image.new('RGBA',(1024,1024), (a,a,a,a)) + for a in range(1,65)] + + # Yeah. Watch some JIT optimize this out. foo = None test_mesh() diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index adf282b03..e3992828a 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -49,14 +49,14 @@ def test_parser(): if EpsImagePlugin.has_ghostscript(): im1, im2 = roundtrip("EPS") - assert_image_similar(im1, im2.convert('L'),20) # EPS comes back in RGB - + assert_image_similar(im1, im2.convert('L'),20) # EPS comes back in RGB + if "jpeg_encoder" in codecs: im1, im2 = roundtrip("JPEG") # lossy compression assert_image(im1, im2.mode, im2.size) # XXX Why assert exception and why does it fail? - # https://github.com/python-imaging/Pillow/issues/78 + # https://github.com/python-pillow/Pillow/issues/78 #assert_exception(IOError, lambda: roundtrip("PDF")) def test_ico(): diff --git a/Tests/test_locale.py b/Tests/test_locale.py index 2683c561b..6b2b95201 100644 --- a/Tests/test_locale.py +++ b/Tests/test_locale.py @@ -3,7 +3,7 @@ from PIL import Image import locale -# ref https://github.com/python-imaging/Pillow/issues/272 +# ref https://github.com/python-pillow/Pillow/issues/272 ## on windows, in polish locale: ## import locale @@ -16,7 +16,7 @@ import locale ## 7 ## 160 -# one of string.whitespace is not freely convertable into ascii. +# one of string.whitespace is not freely convertable into ascii. path = "Images/lena.jpg" diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 4833cabb2..9b7881c23 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -83,12 +83,12 @@ def test_16bit(): def test_to_array(): def _to_array(mode, dtype): - img = lena(mode) + img = lena(mode) np_img = numpy.array(img) _test_img_equals_nparray(img, np_img) assert_equal(np_img.dtype, numpy.dtype(dtype)) - - + + modes = [("L", 'uint8'), ("I", 'int32'), ("F", 'float32'), @@ -101,20 +101,20 @@ def test_to_array(): ("I;16B", '>u2'), ("I;16L", ' If you've discovered a bug, you can - open an issue + open an issue on Github.

diff --git a/docs/about.rst b/docs/about.rst index e8c9356dc..817773610 100644 --- a/docs/about.rst +++ b/docs/about.rst @@ -11,8 +11,8 @@ The fork authors' goal is to foster active development of PIL through: - Regular releases to the `Python Package Index`_ - Solicitation for community contributions and involvement on `Image-SIG`_ -.. _Travis CI: https://travis-ci.org/python-imaging/Pillow -.. _GitHub: https://github.com/python-imaging/Pillow +.. _Travis CI: https://travis-ci.org/python-pillow/Pillow +.. _GitHub: https://github.com/python-pillow/Pillow .. _Python Package Index: https://pypi.python.org/pypi/Pillow .. _Image-SIG: http://mail.python.org/mailman/listinfo/image-sig @@ -60,6 +60,6 @@ announcement. So if you still want to support PIL, please .. _report issues here first: https://bitbucket.org/effbot/pil-2009-raclette/issues -.. _open the corresponding Pillow tickets here: https://github.com/python-imaging/Pillow/issues +.. _open the corresponding Pillow tickets here: https://github.com/python-pillow/Pillow/issues Please provide a link to the PIL ticket so we can track the issue(s) upstream. diff --git a/docs/index.rst b/docs/index.rst index 25e9f6b73..2eb845bff 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,8 +4,8 @@ Pillow Pillow is the 'friendly' PIL fork by Alex Clark and Contributors. PIL is the Python Imaging Library by Fredrik Lundh and Contributors. -.. image:: https://travis-ci.org/python-imaging/Pillow.svg?branch=master - :target: https://travis-ci.org/python-imaging/Pillow +.. image:: https://travis-ci.org/python-pillow/Pillow.svg?branch=master + :target: https://travis-ci.org/python-pillow/Pillow :alt: Travis CI build status .. image:: https://pypip.in/v/Pillow/badge.png @@ -24,7 +24,7 @@ To start using Pillow, please read the :doc:`installation instructions `. You can get the source and contribute at -https://github.com/python-imaging/Pillow. You can download archives +https://github.com/python-pillow/Pillow. You can download archives and old versions from `PyPI `_. .. toctree:: @@ -42,7 +42,7 @@ Support Pillow! PIL needs you! Please help us maintain the Python Imaging Library here: -- `GitHub `_ +- `GitHub `_ - `Freenode `_ - `Image-SIG `_ diff --git a/setup.py b/setup.py index 50ca985e3..83defd82b 100644 --- a/setup.py +++ b/setup.py @@ -234,7 +234,7 @@ class pil_build_ext(build_ext): elif sys.platform.startswith("linux"): arch_tp = (plat.processor(), plat.architecture()[0]) if arch_tp == ("x86_64","32bit"): - # 32 bit build on 64 bit machine. + # 32 bit build on 64 bit machine. _add_directory(library_dirs, "/usr/lib/i386-linux-gnu") else: for platform_ in arch_tp: @@ -339,7 +339,7 @@ class pil_build_ext(build_ext): # on Windows, look for the OpenJPEG libraries in the location that # the official installed puts them if sys.platform == "win32": - _add_directory(library_dirs, + _add_directory(library_dirs, os.path.join(os.environ.get("ProgramFiles", ""), "OpenJPEG 2.0", "lib")) _add_directory(include_dirs, @@ -378,7 +378,7 @@ class pil_build_ext(build_ext): if _find_include_file(self, "openjpeg-2.0/openjpeg.h"): if _find_library_file(self, "openjp2"): feature.jpeg2000 = "openjp2" - + if feature.want('tiff'): if _find_library_file(self, "tiff"): feature.tiff = "tiff" @@ -660,7 +660,7 @@ setup( _read('CHANGES.rst')).decode('utf-8'), author='Alex Clark (fork author)', author_email='aclark@aclark.net', - url='http://python-imaging.github.io/', + url='http://python-pillow.github.io/', classifiers=[ "Development Status :: 6 - Mature", "Topic :: Multimedia :: Graphics", From dc667b1f19b5dd85f97ebd14c7497eaaa218c229 Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 3 Jun 2014 09:01:57 -0400 Subject: [PATCH 30/33] Fix link --- PIL/OleFileIO-README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/PIL/OleFileIO-README.md b/PIL/OleFileIO-README.md index 4a4fdcbca..11a0e9053 100644 --- a/PIL/OleFileIO-README.md +++ b/PIL/OleFileIO-README.md @@ -7,7 +7,7 @@ This is an improved version of the OleFileIO module from [PIL](http://www.python As far as I know, this module is now the most complete and robust Python implementation to read MS OLE2 files, portable on several operating systems. (please tell me if you know other similar Python modules) -OleFileIO_PL can be used as an independent module or with PIL. The goal is to have it integrated into [Pillow](http://python-imaging.github.io/), the friendly fork of PIL. +OleFileIO_PL can be used as an independent module or with PIL. The goal is to have it integrated into [Pillow](http://python-pillow.github.io/), the friendly fork of PIL. OleFileIO\_PL is mostly meant for developers. If you are looking for tools to analyze OLE files or to extract data, then please also check [python-oletools](http://www.decalage.info/python/oletools), which are built upon OleFileIO_PL. @@ -348,4 +348,4 @@ By obtaining, using, and/or copying this software and/or its associated document Permission to use, copy, modify, and distribute this software and its associated documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appears in all copies, and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Secret Labs AB or the author not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. -SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file +SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. From 682ad75759548f0e3cd590fcfb14f396701061e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Boulogne?= Date: Tue, 3 Jun 2014 16:34:23 -0400 Subject: [PATCH 31/33] DOC: fix name in docstring --- PIL/Image.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/PIL/Image.py b/PIL/Image.py index 887ceabc1..e064ed9ef 100644 --- a/PIL/Image.py +++ b/PIL/Image.py @@ -1529,7 +1529,7 @@ class Image: clockwise around its centre. :param angle: In degrees counter clockwise. - :param filter: An optional resampling filter. This can be + :param resample: An optional resampling filter. This can be one of :py:attr:`PIL.Image.NEAREST` (use nearest neighbour), :py:attr:`PIL.Image.BILINEAR` (linear interpolation in a 2x2 environment), or :py:attr:`PIL.Image.BICUBIC` @@ -1550,7 +1550,6 @@ class Image: math.cos(angle), math.sin(angle), 0.0, -math.sin(angle), math.cos(angle), 0.0 ] - def transform(x, y, matrix=matrix): (a, b, c, d, e, f) = matrix From 9497525abcedc7664b5de82f1fad2b8e1294bbad Mon Sep 17 00:00:00 2001 From: Alex Clark Date: Tue, 3 Jun 2014 16:39:17 -0400 Subject: [PATCH 32/33] Fix coveralls.io URLs --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 33b9813d0..0831a6b81 100644 --- a/README.rst +++ b/README.rst @@ -17,7 +17,7 @@ Pillow is the "friendly" PIL fork by Alex Clark and Contributors. PIL is the Pyt :target: https://pypi.python.org/pypi/Pillow/ :alt: Number of PyPI downloads -.. image:: https://coveralls.io/repos/python-imaging/Pillow/badge.png?branch=master - :target: https://coveralls.io/r/python-imaging/Pillow?branch=master +.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master + :target: https://coveralls.io/r/python-pillow/Pillow?branch=master The documentation is hosted at http://pillow.readthedocs.org/. It contains installation instructions, tutorials, reference, compatibility details, and more. From e518879c0ea37be3afe64ac4d8817e4267b551a6 Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 4 Jun 2014 00:11:31 +0300 Subject: [PATCH 33/33] Fix Coveralls link --- docs/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 2eb845bff..a8c204228 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -16,8 +16,8 @@ Python Imaging Library by Fredrik Lundh and Contributors. :target: https://pypi.python.org/pypi/Pillow/ :alt: Number of PyPI downloads -.. image:: https://coveralls.io/repos/python-imaging/Pillow/badge.png?branch=master - :target: https://coveralls.io/r/python-imaging/Pillow?branch=master +.. image:: https://coveralls.io/repos/python-pillow/Pillow/badge.png?branch=master + :target: https://coveralls.io/r/python-pillow/Pillow?branch=master :alt: Test coverage To start using Pillow, please read the :doc:`installation