pilkit-2.0/0000775000175000017500000000000013051563625013145 5ustar venkovenko00000000000000pilkit-2.0/pilkit.egg-info/0000775000175000017500000000000013051563625016133 5ustar venkovenko00000000000000pilkit-2.0/pilkit.egg-info/top_level.txt0000664000175000017500000000000713051563625020662 0ustar venkovenko00000000000000pilkit pilkit-2.0/pilkit.egg-info/PKG-INFO0000664000175000017500000001111113051563625017223 0ustar venkovenko00000000000000Metadata-Version: 1.1 Name: pilkit Version: 2.0 Summary: A collection of utilities and processors for the Python Imaging Libary. Home-page: http://github.com/matthewwithanm/pilkit/ Author: Matthew Tretter Author-email: m@tthewwithanm.com License: BSD Description: PILKit is a collection of utilities for working with PIL (the Python Imaging Library). One of its main features is a set of **processors** which expose a simple interface for performing manipulations on PIL images. Looking for more advanced processors? Check out `Instakit`_! **For the complete documentation on the latest stable version of PILKit, see** `PILKit on RTD`_. .. image:: https://api.travis-ci.org/matthewwithanm/pilkit.png :target: https://travis-ci.org/matthewwithanm/pilkit .. _`PILKit on RTD`: http://pilkit.readthedocs.org .. _`Instakit`: https://github.com/fish2000/instakit Installation ============ 1. Install `PIL`_ or `Pillow`_. 2. Run ``pip install pilkit`` (or clone the source and put the pilkit module on your path) .. note:: If you've never seen Pillow before, it considers itself a more-frequently updated "friendly" fork of PIL that's compatible with setuptools. As such, it shares the same namespace as PIL does and is a drop-in replacement. .. _`PIL`: http://pypi.python.org/pypi/PIL .. _`Pillow`: http://pypi.python.org/pypi/Pillow Usage Overview ============== Processors ---------- The "pilkit.processors" module contains several classes for processing PIL images, which provide an easy to understand API: .. code-block:: python from pilkit.processors import ResizeToFit img = Image.open('/path/to/my/image.png') processor = ResizeToFit(100, 100) new_img = processor.process(img) A few of the included processors are: * ``ResizeToFit`` * ``ResizeToFill`` * ``SmartResize`` * ``Adjust`` * ``TrimBorderColor`` * ``Transpose`` There's also a ``ProcessorPipeline`` class for executing processors sequentially: .. code-block:: python from pilkit.processors import ProcessorPipeline, ResizeToFit, Adjust img = Image.open('/path/to/my/image.png') processor = ProcessorPipeline([Adjust(color=0), ResizeToFit(100, 100)]) new_image = processor.process(img) Utilities --------- In addition to the processors, PILKit contains a few utilities to ease the pain of working with PIL. Some examples: ``prepare_image`` Prepares the image for saving to the provided format by doing some common-sense conversions, including preserving transparency and quantizing. ``save_image`` Wraps PIL's ``Image.save()`` method in order to gracefully handle PIL's "Suspension not allowed here" errors, and (optionally) prepares the image using ``prepare_image`` Utilities are also included for converting between formats, extensions, and mimetypes. Community ========= Please use `the GitHub issue tracker `_ to report bugs. `A mailing list `_ also exists to discuss the project and ask questions, as well as the official `#imagekit `_ channel on Freenode. (Both of these are shared with the `django-imagekit`_ project—from which PILKit spun off.) .. _`django-imagekit`: https://github.com/jdriscoll/django-imagekit Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Topic :: Utilities pilkit-2.0/pilkit.egg-info/not-zip-safe0000664000175000017500000000000113051562731020356 0ustar venkovenko00000000000000 pilkit-2.0/pilkit.egg-info/dependency_links.txt0000664000175000017500000000000113051563625022201 0ustar venkovenko00000000000000 pilkit-2.0/pilkit.egg-info/SOURCES.txt0000664000175000017500000000120613051563625020016 0ustar venkovenko00000000000000AUTHORS LICENSE MANIFEST.in README.rst setup.py docs/Makefile docs/make.bat docs/source/conf.py docs/source/index.rst pilkit/__init__.py pilkit/exceptions.py pilkit/lib.py pilkit/pkgmeta.py pilkit/utils.py pilkit.egg-info/PKG-INFO pilkit.egg-info/SOURCES.txt pilkit.egg-info/dependency_links.txt pilkit.egg-info/not-zip-safe pilkit.egg-info/top_level.txt pilkit/processors/__init__.py pilkit/processors/base.py pilkit/processors/crop.py pilkit/processors/overlay.py pilkit/processors/resize.py pilkit/processors/utils.py tests/__init__.py tests/test_processors.py tests/test_utils.py tests/utils.py tests/assets/cat.gif tests/assets/reference.pngpilkit-2.0/pilkit/0000775000175000017500000000000013051563625014441 5ustar venkovenko00000000000000pilkit-2.0/pilkit/utils.py0000664000175000017500000003004113051561165016146 0ustar venkovenko00000000000000import os import mimetypes import sys from io import UnsupportedOperation from .exceptions import UnknownExtension, UnknownFormat from .lib import Image, ImageFile, StringIO, string_types RGBA_TRANSPARENCY_FORMATS = ['PNG'] PALETTE_TRANSPARENCY_FORMATS = ['PNG', 'GIF'] DEFAULT_EXTENSIONS = { 'JPEG': '.jpg', } def img_to_fobj(img, format, autoconvert=True, **options): return save_image(img, StringIO(), format, options, autoconvert) def open_image(target): target.seek(0) return Image.open(target) _pil_init = 0 def _preinit_pil(): """Loads the standard PIL file format drivers. Returns True if ``preinit()`` was called (and there's a potential that more drivers were loaded) or False if there is no possibility that new drivers were loaded. """ global _pil_init if _pil_init < 1: Image.preinit() _pil_init = 1 return True return False def _init_pil(): """Loads all PIL file format drivers. Returns True if ``init()`` was called (and there's a potential that more drivers were loaded) or False if there is no possibility that new drivers were loaded. """ global _pil_init _preinit_pil() if _pil_init < 2: Image.init() _pil_init = 2 return True return False def _extension_to_format(extension): return Image.EXTENSION.get(extension.lower()) def _format_to_extension(format): if format: format = format.upper() if format in DEFAULT_EXTENSIONS: ext = DEFAULT_EXTENSIONS[format] # It's not enough for an extension to be listed in # ``DEFAULT_EXTENSIONS``, it must also be recognized by PIL. if ext in Image.EXTENSION: return ext for k, v in Image.EXTENSION.items(): if v == format: return k return None def extension_to_mimetype(ext): try: filename = 'a%s' % (ext or '') # guess_type requires a full filename, not just an extension mimetype = mimetypes.guess_type(filename)[0] except IndexError: mimetype = None return mimetype def format_to_mimetype(format): return extension_to_mimetype(format_to_extension(format)) def extension_to_format(extension): """Returns the format that corresponds to the provided extension. """ format = _extension_to_format(extension) if not format and _preinit_pil(): format = _extension_to_format(extension) if not format and _init_pil(): format = _extension_to_format(extension) if not format: raise UnknownExtension(extension) return format def format_to_extension(format): """Returns the first extension that matches the provided format. """ extension = None if format: extension = _format_to_extension(format) if not extension and _preinit_pil(): extension = _format_to_extension(format) if not extension and _init_pil(): extension = _format_to_extension(format) if not extension: raise UnknownFormat(format) return extension def suggest_extension(name, format): original_extension = os.path.splitext(name)[1] try: suggested_extension = format_to_extension(format) except UnknownFormat: extension = original_extension else: if suggested_extension.lower() == original_extension.lower(): extension = original_extension else: try: original_format = extension_to_format(original_extension) except UnknownExtension: extension = suggested_extension else: # If the formats match, give precedence to the original extension. if format.lower() == original_format.lower(): extension = original_extension else: extension = suggested_extension return extension class FileWrapper(object): def __init__(self, wrapped): super(FileWrapper, self).__setattr__('_wrapped', wrapped) def fileno(self): try: return self._wrapped.fileno() except UnsupportedOperation: raise AttributeError def __getattr__(self, name): return getattr(self._wrapped, name) def __setattr__(self, name, value): return setattr(self._wrapped, name, value) def __delattr__(self, key): return delattr(self._wrapped, key) def save_image(img, outfile, format, options=None, autoconvert=True): """ Wraps PIL's ``Image.save()`` method. There are two main benefits of using this function over PIL's: 1. It gracefully handles the infamous "Suspension not allowed here" errors. 2. It prepares the image for saving using ``prepare_image()``, which will do some common-sense processing given the target format. """ options = options or {} if autoconvert: img, save_kwargs = prepare_image(img, format) # Use returned from prepare_image arguments for base # and update them with provided options. Then use the result save_kwargs.update(options) options = save_kwargs # Attempt to reset the file pointer. try: outfile.seek(0) except AttributeError: pass def save(fp): with quiet(): img.save(fp, format, **options) # Some versions of PIL only catch AttributeErrors where they should also # catch UnsupportedOperation exceptions. To work around this, we wrap the # file with an object that will raise the type of error it wants. if any(isinstance(outfile, t) for t in string_types): # ...but don't wrap strings. wrapper = outfile else: wrapper = FileWrapper(outfile) try: save(wrapper) except IOError: # PIL can have problems saving large JPEGs if MAXBLOCK isn't big enough, # So if we have a problem saving, we temporarily increase it. See # http://github.com/matthewwithanm/django-imagekit/issues/50 # https://github.com/matthewwithanm/django-imagekit/issues/134 # https://github.com/python-imaging/Pillow/issues/148 # https://github.com/matthewwithanm/pilkit/commit/0f914e8b40e3d30f28e04ffb759b262aa8a1a082#commitcomment-3885362 # MAXBLOCK must be at least as big as... new_maxblock = max( (len(options['exif']) if 'exif' in options else 0) + 5, # ...the entire exif header block img.size[0] * 4, # ...a complete scan line 3 * img.size[0] * img.size[1], # ...3 bytes per every pixel in the image ) if new_maxblock < ImageFile.MAXBLOCK: raise old_maxblock = ImageFile.MAXBLOCK ImageFile.MAXBLOCK = new_maxblock try: save(wrapper) finally: ImageFile.MAXBLOCK = old_maxblock try: outfile.seek(0) except AttributeError: pass return outfile class quiet(object): """ A context manager for suppressing the stderr activity of PIL's C libraries. Based on http://stackoverflow.com/a/978264/155370 """ def __enter__(self): try: self.stderr_fd = sys.__stderr__.fileno() except AttributeError: # In case of Azure, the file descriptor is not present so we can return # from here return try: self.null_fd = os.open(os.devnull, os.O_RDWR) except OSError: # If dev/null isn't writeable, then they just have to put up with # the noise. return self.old = os.dup(self.stderr_fd) os.dup2(self.null_fd, self.stderr_fd) def __exit__(self, *args, **kwargs): if not getattr(self, 'null_fd', None): return if not getattr(self, 'old', None): return os.dup2(self.old, self.stderr_fd) os.close(self.null_fd) os.close(self.old) def prepare_image(img, format): """ Prepares the image for saving to the provided format by doing some common-sense conversions. This includes things like preserving transparency and quantizing. This function is used automatically by ``save_image()`` immediately before saving unless you specify ``autoconvert=False``. It is provided as a utility for those doing their own processing. :param img: The image to prepare for saving. :param format: The format that the image will be saved to. """ make_opaque = False save_kwargs = {} format = format.upper() if img.mode == 'RGBA': if format in RGBA_TRANSPARENCY_FORMATS: pass elif format in PALETTE_TRANSPARENCY_FORMATS: # If you're going from a format with alpha transparency to one # with palette transparency, transparency values will be # snapped: pixels that are more opaque than not will become # fully opaque; pixels that are more transparent than not will # become fully transparent. This will not produce a good-looking # result if your image contains varying levels of opacity; in # that case, you'll probably want to use a processor to composite # the image on a solid color. The reason we don't do this by # default is because not doing so allows processors to treat # RGBA-format images as a super-type of P-format images: if you # have an RGBA-format image with only a single transparent # color, and save it as a GIF, it will retain its transparency. # In other words, a P-format image converted to an # RGBA-formatted image by a processor and then saved as a # P-format image will give the expected results. # Work around a bug in PIL: split() doesn't check to see if # img is loaded. img.load() alpha = img.split()[-1] mask = Image.eval(alpha, lambda a: 255 if a <= 128 else 0) img = img.convert('RGB').convert('P', palette=Image.ADAPTIVE, colors=255) img.paste(255, mask) save_kwargs['transparency'] = 255 else: # Simply converting an RGBA-format image to an RGB one creates a # gross result, so we paste the image onto a white background. If # that's not what you want, that's fine: use a processor to deal # with the transparency however you want. This is simply a # sensible default that will always produce something that looks # good. Or at least, it will look better than just a straight # conversion. make_opaque = True elif img.mode == 'P': if format in PALETTE_TRANSPARENCY_FORMATS: try: save_kwargs['transparency'] = img.info['transparency'] except KeyError: pass elif format in RGBA_TRANSPARENCY_FORMATS: # Currently PIL doesn't support any RGBA-mode formats that # aren't also P-mode formats, so this will never happen. img = img.convert('RGBA') else: make_opaque = True else: img = img.convert('RGB') # GIFs are always going to be in palette mode, so we can do a little # optimization. Note that the RGBA sources also use adaptive # quantization (above). Images that are already in P mode don't need # any quantization because their colors are already limited. if format == 'GIF': img = img.convert('P', palette=Image.ADAPTIVE) if make_opaque: from .processors import MakeOpaque img = MakeOpaque().process(img).convert('RGB') if format == 'JPEG': save_kwargs['optimize'] = True return img, save_kwargs def process_image(img, processors=None, format=None, autoconvert=True, options=None): from .processors import ProcessorPipeline original_format = img.format # Run the processors img = ProcessorPipeline(processors or []).process(img) format = format or img.format or original_format or 'JPEG' options = options or {} return img_to_fobj(img, format, autoconvert, **options) pilkit-2.0/pilkit/pkgmeta.py0000664000175000017500000000024013051562306016432 0ustar venkovenko00000000000000__title__ = 'pilkit' __author__ = 'Matthew Tretter' __version__ = '2.0' __license__ = 'BSD' __all__ = ['__title__', '__author__', '__version__', '__license__'] pilkit-2.0/pilkit/exceptions.py0000664000175000017500000000012713051561165017171 0ustar venkovenko00000000000000class UnknownExtension(Exception): pass class UnknownFormat(Exception): pass pilkit-2.0/pilkit/lib.py0000664000175000017500000000176613051561165015570 0ustar venkovenko00000000000000# flake8: noqa # Required PIL classes may or may not be available from the root namespace # depending on the installation method used. try: from PIL import Image, ImageColor, ImageChops, ImageEnhance, ImageFile, \ ImageFilter, ImageDraw, ImageStat except ImportError: try: import Image import ImageColor import ImageChops import ImageEnhance import ImageFile import ImageFilter import ImageDraw import ImageStat except ImportError: raise ImportError('PILKit was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path.') try: from io import BytesIO as StringIO except ImportError as exc: try: from cStringIO import StringIO except ImportError: try: from StringIO import StringIO except ImportError: raise exc try: string_types = [basestring, str] except NameError: string_types = [str] pilkit-2.0/pilkit/__init__.py0000664000175000017500000000004713051561165016550 0ustar venkovenko00000000000000# flake8: noqa from .pkgmeta import * pilkit-2.0/pilkit/processors/0000775000175000017500000000000013051563625016643 5ustar venkovenko00000000000000pilkit-2.0/pilkit/processors/utils.py0000664000175000017500000000106713051561165020356 0ustar venkovenko00000000000000import math from ..lib import Image def histogram_entropy(im): """ Calculate the entropy of an images' histogram. Used for "smart cropping" in easy-thumbnails; see: https://raw.github.com/SmileyChris/easy-thumbnails/master/easy_thumbnails/utils.py """ if not isinstance(im, Image.Image): return 0 # Fall back to a constant entropy. histogram = im.histogram() hist_ceil = float(sum(histogram)) histonorm = [histocol / hist_ceil for histocol in histogram] return -sum([p * math.log(p, 2) for p in histonorm if p != 0]) pilkit-2.0/pilkit/processors/resize.py0000664000175000017500000002314413051561165020517 0ustar venkovenko00000000000000from .base import Anchor from ..lib import Image class Resize(object): """ Resizes an image to the specified width and height. """ def __init__(self, width, height, upscale=True): """ :param width: The target width, in pixels. :param height: The target height, in pixels. :param upscale: Should the image be enlarged if smaller than the dimensions? """ self.width = width self.height = height self.upscale = upscale def process(self, img): if self.upscale or (self.width < img.size[0] and self.height < img.size[1]): img = img.convert('RGBA') img = img.resize((self.width, self.height), Image.ANTIALIAS) return img class ResizeToCover(object): """ Resizes the image to the smallest possible size that will entirely cover the provided dimensions. You probably won't be using this processor directly, but it's used internally by ``ResizeToFill`` and ``SmartResize``. """ def __init__(self, width, height, upscale=True): """ :param width: The target width, in pixels. :param height: The target height, in pixels. """ self.width, self.height = width, height self.upscale = upscale def process(self, img): original_width, original_height = img.size ratio = max(float(self.width) / original_width, float(self.height) / original_height) new_width, new_height = (int(round(original_width * ratio)), int(round(original_height * ratio))) img = Resize(new_width, new_height, upscale=self.upscale).process(img) return img class ResizeToFill(object): """ Resizes an image, cropping it to the exact specified width and height. """ def __init__(self, width=None, height=None, anchor=None, upscale=True): """ :param width: The target width, in pixels. :param height: The target height, in pixels. :param anchor: Specifies which part of the image should be retained when cropping. :param upscale: Should the image be enlarged if smaller than the dimensions? """ self.width = width self.height = height self.anchor = anchor self.upscale = upscale def process(self, img): from .crop import Crop img = ResizeToCover(self.width, self.height, upscale=self.upscale).process(img) return Crop(self.width, self.height, anchor=self.anchor).process(img) class SmartResize(object): """ The ``SmartResize`` processor is identical to ``ResizeToFill``, except that it uses entropy to crop the image instead of a user-specified anchor point. Internally, it simply runs the ``ResizeToCover`` and ``SmartCrop`` processors in series. """ def __init__(self, width, height, upscale=True): """ :param width: The target width, in pixels. :param height: The target height, in pixels. :param upscale: Should the image be enlarged if smaller than the dimensions? """ self.width, self.height = width, height self.upscale = upscale def process(self, img): from .crop import SmartCrop img = ResizeToCover(self.width, self.height, upscale=self.upscale).process(img) return SmartCrop(self.width, self.height).process(img) class ResizeCanvas(object): """ Resizes the canvas, using the provided background color if the new size is larger than the current image. """ def __init__(self, width, height, color=None, anchor=None, x=None, y=None): """ :param width: The target width, in pixels. :param height: The target height, in pixels. :param color: The background color to use for padding. :param anchor: Specifies the position of the original image on the new canvas. Valid values are: - Anchor.TOP_LEFT - Anchor.TOP - Anchor.TOP_RIGHT - Anchor.LEFT - Anchor.CENTER - Anchor.RIGHT - Anchor.BOTTOM_LEFT - Anchor.BOTTOM - Anchor.BOTTOM_RIGHT You may also pass a tuple that indicates the position in percentages. For example, ``(0, 0)`` corresponds to "top left", ``(0.5, 0.5)`` to "center" and ``(1, 1)`` to "bottom right". This is basically the same as using percentages in CSS background positions. """ if x is not None or y is not None: if anchor: raise Exception('You may provide either an anchor or x and y' ' coordinate, but not both.') else: self.x, self.y = x or 0, y or 0 self.anchor = None else: self.anchor = anchor or Anchor.CENTER self.x = self.y = None self.width = width self.height = height self.color = color or (255, 255, 255, 0) def process(self, img): original_width, original_height = img.size if self.anchor: anchor = Anchor.get_tuple(self.anchor) trim_x, trim_y = self.width - original_width, \ self.height - original_height x = int(float(trim_x) * float(anchor[0])) y = int(float(trim_y) * float(anchor[1])) else: x, y = self.x, self.y new_img = Image.new('RGBA', (self.width, self.height), self.color) new_img.paste(img, (x, y)) return new_img class AddBorder(object): """ Add a border of specific color and size to an image. """ def __init__(self, thickness, color=None): """ :param color: Color to use for the border :param thickness: Thickness of the border. Can be either an int or a 4-tuple of ints of the form (top, right, bottom, left). """ self.color = color if isinstance(thickness, int): self.top = self.right = self.bottom = self.left = thickness else: self.top, self.right, self.bottom, self.left = thickness def process(self, img): new_width = img.size[0] + self.left + self.right new_height = img.size[1] + self.top + self.bottom return ResizeCanvas(new_width, new_height, color=self.color, x=self.left, y=self.top).process(img) class ResizeToFit(object): """ Resizes an image to fit within the specified dimensions. """ def __init__(self, width=None, height=None, upscale=True, mat_color=None, anchor=Anchor.CENTER): """ :param width: The maximum width of the desired image. :param height: The maximum height of the desired image. :param upscale: A boolean value specifying whether the image should be enlarged if its dimensions are smaller than the target dimensions. :param mat_color: If set, the target image size will be enforced and the specified color will be used as a background color to pad the image. """ self.width = width self.height = height self.upscale = upscale self.mat_color = mat_color self.anchor = anchor def process(self, img): cur_width, cur_height = img.size if not self.width is None and not self.height is None: ratio = min(float(self.width) / cur_width, float(self.height) / cur_height) else: if self.width is None: ratio = float(self.height) / cur_height else: ratio = float(self.width) / cur_width new_dimensions = (int(round(cur_width * ratio)), int(round(cur_height * ratio))) img = Resize(new_dimensions[0], new_dimensions[1], upscale=self.upscale).process(img) if self.mat_color is not None: img = ResizeCanvas(self.width, self.height, self.mat_color, anchor=self.anchor).process(img) return img class Thumbnail(object): """ Resize the image for use as a thumbnail. Wraps ``ResizeToFill``, ``ResizeToFit``, and ``SmartResize``. Note: while it doesn't currently, in the future this processor may also sharpen based on the amount of reduction. """ def __init__(self, width=None, height=None, anchor=None, crop=None, upscale=None): self.width = width self.height = height self.upscale = upscale if anchor: if crop is False: raise Exception("You can't specify an anchor point if crop is False.") else: crop = True elif crop is None: # Assume we are cropping if both a width and height are provided. If # only one is, we must be resizing to fit. crop = width is not None and height is not None # A default anchor if cropping. if crop and anchor is None: anchor = 'auto' self.crop = crop self.anchor = anchor def process(self, img): if self.crop: if not self.width or not self.height: raise Exception('You must provide both a width and height when' ' cropping.') if self.anchor == 'auto': processor = SmartResize(width=self.width, height=self.height, upscale=self.upscale) else: processor = ResizeToFill(width=self.width, height=self.height, anchor=self.anchor, upscale=self.upscale) else: processor = ResizeToFit(width=self.width, height=self.height, upscale=self.upscale) return processor.process(img) pilkit-2.0/pilkit/processors/crop.py0000664000175000017500000001344713051561165020166 0ustar venkovenko00000000000000from .base import Anchor # noqa from .utils import histogram_entropy from ..lib import Image, ImageChops, ImageDraw, ImageStat class Side(object): TOP = 't' RIGHT = 'r' BOTTOM = 'b' LEFT = 'l' ALL = (TOP, RIGHT, BOTTOM, LEFT) def _crop(img, bbox, sides=Side.ALL): bbox = ( bbox[0] if Side.LEFT in sides else 0, bbox[1] if Side.TOP in sides else 0, bbox[2] if Side.RIGHT in sides else img.size[0], bbox[3] if Side.BOTTOM in sides else img.size[1], ) return img.crop(bbox) def detect_border_color(img): mask = Image.new('1', img.size, 1) w, h = img.size[0] - 2, img.size[1] - 2 if w > 0 and h > 0: draw = ImageDraw.Draw(mask) draw.rectangle([1, 1, w, h], 0) return ImageStat.Stat(img.convert('RGBA').histogram(mask)).median class TrimBorderColor(object): """Trims a color from the sides of an image. """ def __init__(self, color=None, tolerance=0.3, sides=Side.ALL): """ :param color: The color to trim from the image, in a 4-tuple RGBA value, where each component is an integer between 0 and 255, inclusive. If no color is provided, the processor will attempt to detect the border color automatically. :param tolerance: A number between 0 and 1 where 0. Zero is the least tolerant and one is the most. :param sides: A list of sides that should be trimmed. Possible values are provided by the :class:`Side` enum class. """ self.color = color self.sides = sides self.tolerance = tolerance def process(self, img): source = img.convert('RGBA') border_color = self.color or tuple(detect_border_color(source)) bg = Image.new('RGBA', img.size, border_color) diff = ImageChops.difference(source, bg) if self.tolerance not in (0, 1): # If tolerance is zero, we've already done the job. A tolerance of # one would mean to trim EVERY color, and since that would result # in a zero-sized image, we just ignore it. if not 0 <= self.tolerance <= 1: raise ValueError('%s is an invalid tolerance. Acceptable values' ' are between 0 and 1 (inclusive).' % self.tolerance) tmp = ImageChops.constant(diff, int(self.tolerance * 255)) \ .convert('RGBA') diff = ImageChops.subtract(diff, tmp) bbox = diff.getbbox() if bbox: img = _crop(img, bbox, self.sides) return img class Crop(object): """ Crops an image, cropping it to the specified width and height. You may optionally provide either an anchor or x and y coordinates. This processor functions exactly the same as ``ResizeCanvas`` except that it will never enlarge the image. """ def __init__(self, width=None, height=None, anchor=None, x=None, y=None): self.width = width self.height = height self.anchor = anchor self.x = x self.y = y def process(self, img): from .resize import ResizeCanvas original_width, original_height = img.size new_width, new_height = min(original_width, self.width), \ min(original_height, self.height) return ResizeCanvas(new_width, new_height, anchor=self.anchor, x=self.x, y=self.y).process(img) class SmartCrop(object): """ Crop an image to the specified dimensions, whittling away the parts of the image with the least entropy. Based on smart crop implementation from easy-thumbnails: https://github.com/SmileyChris/easy-thumbnails/blob/master/easy_thumbnails/processors.py#L193 """ def __init__(self, width=None, height=None): """ :param width: The target width, in pixels. :param height: The target height, in pixels. """ self.width = width self.height = height def compare_entropy(self, start_slice, end_slice, slice, difference): """ Calculate the entropy of two slices (from the start and end of an axis), returning a tuple containing the amount that should be added to the start and removed from the end of the axis. """ start_entropy = histogram_entropy(start_slice) end_entropy = histogram_entropy(end_slice) if end_entropy and abs(start_entropy / end_entropy - 1) < 0.01: # Less than 1% difference, remove from both sides. if difference >= slice * 2: return slice, slice half_slice = slice // 2 return half_slice, slice - half_slice if start_entropy > end_entropy: return 0, slice else: return slice, 0 def process(self, img): source_x, source_y = img.size diff_x = int(source_x - min(source_x, self.width)) diff_y = int(source_y - min(source_y, self.height)) left = top = 0 right, bottom = source_x, source_y while diff_x: slice = min(diff_x, max(diff_x // 5, 10)) start = img.crop((left, 0, left + slice, source_y)) end = img.crop((right - slice, 0, right, source_y)) add, remove = self.compare_entropy(start, end, slice, diff_x) left += add right -= remove diff_x = diff_x - add - remove while diff_y: slice = min(diff_y, max(diff_y // 5, 10)) start = img.crop((0, top, source_x, top + slice)) end = img.crop((0, bottom - slice, source_x, bottom)) add, remove = self.compare_entropy(start, end, slice, diff_y) top += add bottom -= remove diff_y = diff_y - add - remove box = (left, top, right, bottom) img = img.crop(box) return img pilkit-2.0/pilkit/processors/__init__.py0000664000175000017500000000060213051561165020747 0ustar venkovenko00000000000000# flake8: noqa """ PILKit image processors. A processor accepts an image, does some stuff, and returns the result. Processors can do anything with the image you want, but their responsibilities should be limited to image manipulations--they should be completely decoupled from the filesystem. """ from .base import * from .crop import * from .overlay import * from .resize import * pilkit-2.0/pilkit/processors/base.py0000664000175000017500000001702413051561165020130 0ustar venkovenko00000000000000from pilkit.lib import Image, ImageColor, ImageEnhance class ProcessorPipeline(list): """ A :class:`list` of other processors. This class allows any object that knows how to deal with a single processor to deal with a list of them. For example:: processed_image = ProcessorPipeline([ProcessorA(), ProcessorB()]).process(image) """ def process(self, img): for proc in self: img = proc.process(img) return img class Adjust(object): """ Performs color, brightness, contrast, and sharpness enhancements on the image. See :mod:`PIL.ImageEnhance` for more imformation. """ def __init__(self, color=1.0, brightness=1.0, contrast=1.0, sharpness=1.0): """ :param color: A number between 0 and 1 that specifies the saturation of the image. 0 corresponds to a completely desaturated image (black and white) and 1 to the original color. See :class:`PIL.ImageEnhance.Color` :param brightness: A number representing the brightness; 0 results in a completely black image whereas 1 corresponds to the brightness of the original. See :class:`PIL.ImageEnhance.Brightness` :param contrast: A number representing the contrast; 0 results in a completely gray image whereas 1 corresponds to the contrast of the original. See :class:`PIL.ImageEnhance.Contrast` :param sharpness: A number representing the sharpness; 0 results in a blurred image; 1 corresponds to the original sharpness; 2 results in a sharpened image. See :class:`PIL.ImageEnhance.Sharpness` """ self.color = color self.brightness = brightness self.contrast = contrast self.sharpness = sharpness def process(self, img): original = img = img.convert('RGBA') for name in ['Color', 'Brightness', 'Contrast', 'Sharpness']: factor = getattr(self, name.lower()) if factor != 1.0: try: img = getattr(ImageEnhance, name)(img).enhance(factor) except ValueError: pass else: # PIL's Color and Contrast filters both convert the image # to L mode, losing transparency info, so we put it back. # See https://github.com/jdriscoll/django-imagekit/issues/64 if name in ('Color', 'Contrast'): img = Image.merge('RGBA', img.split()[:3] + original.split()[3:4]) return img class Reflection(object): """ Creates an image with a reflection. """ def __init__(self, background_color='#FFFFFF', size=0.0, opacity=0.6): self.background_color = background_color self.size = size self.opacity = opacity def process(self, img): # Convert bgcolor string to RGB value. background_color = ImageColor.getrgb(self.background_color) # Handle palleted images. img = img.convert('RGBA') # Copy orignial image and flip the orientation. reflection = img.copy().transpose(Image.FLIP_TOP_BOTTOM) # Create a new image filled with the bgcolor the same size. background = Image.new("RGBA", img.size, background_color) # Calculate our alpha mask. start = int(255 - (255 * self.opacity)) # The start of our gradient. steps = int(255 * self.size) # The number of intermedite values. increment = (255 - start) / float(steps) mask = Image.new('L', (1, 255)) for y in range(255): if y < steps: val = int(y * increment + start) else: val = 255 mask.putpixel((0, y), val) alpha_mask = mask.resize(img.size) # Merge the reflection onto our background color using the alpha mask. reflection = Image.composite(background, reflection, alpha_mask) # Crop the reflection. reflection_height = int(img.size[1] * self.size) reflection = reflection.crop((0, 0, img.size[0], reflection_height)) # Create new image sized to hold both the original image and # the reflection. composite = Image.new("RGBA", (img.size[0], img.size[1] + reflection_height), background_color) # Paste the orignal image and the reflection into the composite image. composite.paste(img, (0, 0)) composite.paste(reflection, (0, img.size[1])) # Return the image complete with reflection effect. return composite class Transpose(object): """ Rotates or flips the image. """ AUTO = 'auto' FLIP_HORIZONTAL = Image.FLIP_LEFT_RIGHT FLIP_VERTICAL = Image.FLIP_TOP_BOTTOM ROTATE_90 = Image.ROTATE_90 ROTATE_180 = Image.ROTATE_180 ROTATE_270 = Image.ROTATE_270 methods = [AUTO] _EXIF_ORIENTATION_STEPS = { 1: [], 2: [FLIP_HORIZONTAL], 3: [ROTATE_180], 4: [FLIP_VERTICAL], 5: [ROTATE_270, FLIP_HORIZONTAL], 6: [ROTATE_270], 7: [ROTATE_90, FLIP_HORIZONTAL], 8: [ROTATE_90], } def __init__(self, *args): """ Possible arguments: - Transpose.AUTO - Transpose.FLIP_HORIZONTAL - Transpose.FLIP_VERTICAL - Transpose.ROTATE_90 - Transpose.ROTATE_180 - Transpose.ROTATE_270 The order of the arguments dictates the order in which the Transposition steps are taken. If Transpose.AUTO is present, all other arguments are ignored, and the processor will attempt to rotate the image according to the EXIF Orientation data. """ super(Transpose, self).__init__() if args: self.methods = args def process(self, img): if self.AUTO in self.methods: try: orientation = img._getexif()[0x0112] ops = self._EXIF_ORIENTATION_STEPS[orientation] except (IndexError, KeyError, TypeError, AttributeError): ops = [] else: ops = self.methods for method in ops: img = img.transpose(method) return img class Anchor(object): """ Defines all the anchor points needed by the various processor classes. """ TOP_LEFT = 'tl' TOP = 't' TOP_RIGHT = 'tr' BOTTOM_LEFT = 'bl' BOTTOM = 'b' BOTTOM_RIGHT = 'br' CENTER = 'c' LEFT = 'l' RIGHT = 'r' _ANCHOR_PTS = { TOP_LEFT: (0, 0), TOP: (0.5, 0), TOP_RIGHT: (1, 0), LEFT: (0, 0.5), CENTER: (0.5, 0.5), RIGHT: (1, 0.5), BOTTOM_LEFT: (0, 1), BOTTOM: (0.5, 1), BOTTOM_RIGHT: (1, 1), } @staticmethod def get_tuple(anchor): """Normalizes anchor values (strings or tuples) to tuples. """ # If the user passed in one of the string values, convert it to a # percentage tuple. if anchor in Anchor._ANCHOR_PTS.keys(): anchor = Anchor._ANCHOR_PTS[anchor] return anchor class MakeOpaque(object): """ Pastes the provided image onto an image of a solid color. Used for when you want to make transparent images opaque. """ def __init__(self, background_color=(255, 255, 255)): self.background_color = background_color def process(self, img): img = img.convert('RGBA') new_img = Image.new('RGBA', img.size, self.background_color) new_img.paste(img, img) return new_img pilkit-2.0/pilkit/processors/overlay.py0000664000175000017500000000135713051561165020701 0ustar venkovenko00000000000000from pilkit.lib import Image class ColorOverlay(object): """ Overlay a color mask with a the given opacity """ def __init__(self, color, overlay_opacity=0.5): """ :pamra color: `ImageColor` instance to overlay on the original image :param overlay_opacity: Define the fusion factor for the overlay mask """ self.color = color self.overlay_opacity = overlay_opacity def process(self, img): original = img = img.convert('RGB') overlay = Image.new('RGB', original.size, self.color) mask = Image.new('RGBA', original.size, (0,0,0,int((1.0 - self.overlay_opacity)*255))) img = Image.composite(original, overlay, mask).convert('RGB') return img pilkit-2.0/LICENSE0000664000175000017500000000276113051561165014155 0ustar venkovenko00000000000000Copyright (c) 2013 Primary Maintainers All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of PILKit nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pilkit-2.0/docs/0000775000175000017500000000000013051563625014075 5ustar venkovenko00000000000000pilkit-2.0/docs/make.bat0000664000175000017500000001176113051561165015505 0ustar venkovenko00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source set I18NSPHINXOPTS=%SPHINXOPTS% source if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PILKit.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PILKit.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end pilkit-2.0/docs/Makefile0000664000175000017500000001270513051561165015537 0ustar venkovenko00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PILKit.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PILKit.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/PILKit" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PILKit" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." pilkit-2.0/docs/source/0000775000175000017500000000000013051563625015375 5ustar venkovenko00000000000000pilkit-2.0/docs/source/index.rst0000664000175000017500000000037413051561165017237 0ustar venkovenko00000000000000 Welcome to PILKit's documentation! ================================== .. include:: ../../README.rst Authors ======= .. include:: ../../AUTHORS Contents ========= * :ref:`genindex` * :ref:`modindex` * :ref:`search` .. toctree:: :maxdepth: 2 pilkit-2.0/docs/source/conf.py0000664000175000017500000001752613051561165016704 0ustar venkovenko00000000000000# -*- coding: utf-8 -*- # # PILKit documentation build configuration file, created by # sphinx-quickstart on Thu Feb 7 20:01:22 2013. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import re, sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'PILKit' copyright = u'2013, Matthew Tretter' pkgmeta = {} pkgmeta_file = os.path.join(os.path.dirname(__file__), '..', '..', 'pilkit', 'pkgmeta.py') with open(pkgmeta_file) as f: code = compile(f.read(), 'pkgmeta.py', 'exec') exec(code, pkgmeta) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = re.match('\d+\.\d+', pkgmeta['__version__']).group() # The full version, including alpha/beta/rc tags. release = pkgmeta['__version__'] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'PILKitdoc' # -- Options for LaTeX output -------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'PILKit.tex', u'PILKit Documentation', u'Matthew Tretter', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'pilkit', u'PILKit Documentation', [u'Matthew Tretter'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'PILKit', u'PILKit Documentation', u'Matthew Tretter', 'PILKit', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' pilkit-2.0/tests/0000775000175000017500000000000013051563625014307 5ustar venkovenko00000000000000pilkit-2.0/tests/test_processors.py0000664000175000017500000001152413051561165020122 0ustar venkovenko00000000000000from pilkit.lib import Image, ImageDraw, ImageColor from pilkit.processors import (Resize, ResizeToFill, ResizeToFit, SmartCrop, SmartResize, MakeOpaque, ColorOverlay) from nose.tools import eq_, assert_true import os from pilkit.processors.resize import Thumbnail from .utils import create_image import mock def test_smartcrop(): img = SmartCrop(100, 100).process(create_image()) eq_(img.size, (100, 100)) def test_resizetofill(): img = ResizeToFill(100, 100).process(create_image()) eq_(img.size, (100, 100)) def test_resizetofit(): # First create an image with aspect ratio 2:1... img = Image.new('RGB', (200, 100)) # ...then resize it to fit within a 100x100 canvas. img = ResizeToFit(100, 100).process(img) # Assert that the image has maintained the aspect ratio. eq_(img.size, (100, 50)) def test_resize_rounding(): """ Regression test for matthewwithanm/pilkit#1 """ img = Image.new('RGB', (95, 95)) img = ResizeToFill(28, 28).process(img) eq_(img.size, (28, 28)) def test_resizetofit_mat(): img = Image.new('RGB', (200, 100)) img = ResizeToFit(100, 100, mat_color=0x000000).process(img) eq_(img.size, (100, 100)) def test_coloroverlay(): """ Test that the ColorOverlay processor """ img = Image.new('RGB', (200, 100)) color = ImageColor.getrgb('#cc0000') img = ColorOverlay(color, overlay_opacity=1.0).process(img) eq_(img.getpixel((0,0)), (204, 0, 0)) def test_resize_antialiasing(): """ Test that the Resize processor antialiases. The Resize processor is used by all of the Resize* variants, so this should cover all of resize processors. Basically, this is to test that it converts to RGBA mode before resizing. Related: jdriscoll/django-imagekit#192 """ # Create a palette image and draw a circle into it. img = Image.new('P', (500, 500), 1) img.putpalette([ 0, 0, 0, 255, 255, 255, 0, 0, 255, ]) d = ImageDraw.ImageDraw(img) d.ellipse((100, 100, 400, 400), fill=2) # Resize the image using the Resize processor img = Resize(100, 100).process(img) # Count the number of colors color_count = len(list(filter(None, img.histogram()))) assert_true(color_count > 2) def test_upscale(): """ Test that the upscale argument works as expected. """ img = Image.new('RGB', (100, 100)) for P in [Resize, ResizeToFit, ResizeToFill, SmartResize]: img2 = P(500, 500, upscale=True).process(img) eq_(img2.size, (500, 500)) img2 = P(500, 500, upscale=False).process(img) eq_(img2.size, (100, 100)) def test_should_raise_exception_if_anchor_is_passed_and_crop_is_set_to_false(): try: Thumbnail(height=200, width=200, upscale=False, crop=False, anchor='t') except Exception as e: eq_(str(e), "You can't specify an anchor point if crop is False.") def test_should_set_crop_to_true_if_anchor_is_passed_without_crop(): thumb = Thumbnail(height=200, width=200, upscale=False, anchor='t') assert_true(thumb.crop) def test_should_raise_exception_when_crop_is_passed_without_height_and_width(): img = Image.new('RGB', (100, 100)) try: Thumbnail(crop=True).process(img) except Exception as e: eq_(str(e), 'You must provide both a width and height when cropping.') @mock.patch('pilkit.processors.resize.SmartResize') def test_should_call_smartresize_when_crop_not_passed(my_mock): img = Image.new('RGB', (100, 100)) Thumbnail(height=200, width=200, upscale=False).process(img) assert_true(my_mock.called) @mock.patch('pilkit.processors.resize.SmartResize') def test_should_repass_upscale_option_true(my_mock): img = Image.new('RGB', (100, 100)) Thumbnail(height=200, width=200, upscale=True).process(img) my_mock.assert_called_once_with(width=200, upscale=True, height=200) @mock.patch('pilkit.processors.resize.SmartResize') def test_should_repass_upscale_option_false(my_mock): img = Image.new('RGB', (100, 100)) Thumbnail(height=200, width=200, upscale=False).process(img) my_mock.assert_called_once_with(width=200, upscale=False, height=200) @mock.patch('pilkit.processors.resize.ResizeToFill') def test_should_call_resizetofill_when_crop_and_ancho_is_passed(my_mock): img = Image.new('RGB', (100, 100)) Thumbnail(height=200, width=200, anchor='fake').process(img) assert_true(my_mock.called) @mock.patch('pilkit.processors.resize.ResizeToFit') def test_should_call_resizetofit_when_crop_is_not_passed(my_mock): img = Image.new('RGB', (100, 100)) Thumbnail(height=200, width=200, crop=False).process(img) assert_true(my_mock.called) def test_make_gifs_opaque(): dir = os.path.dirname(__file__) path = os.path.join(dir, 'assets', 'cat.gif') gif = Image.open(path) MakeOpaque().process(gif) pilkit-2.0/tests/utils.py0000664000175000017500000000060013051561165016012 0ustar venkovenko00000000000000import os from pilkit.lib import Image def get_image_file(): """ See also: http://en.wikipedia.org/wiki/Lenna http://sipi.usc.edu/database/database.php?volume=misc&image=12 """ dir = os.path.dirname(__file__) path = os.path.join(dir, 'assets', 'reference.png') return open(path, 'r+b') def create_image(): return Image.open(get_image_file()) pilkit-2.0/tests/assets/0000775000175000017500000000000013051563625015611 5ustar venkovenko00000000000000pilkit-2.0/tests/assets/reference.png0000664000175000017500000035117613051561165020267 0ustar venkovenko00000000000000PNG  IHDR?1IDATxlYmr&}_s{K^I"KF (A2,0 /~'a TK%Tl.yo;uguZp HfP z`{F)3<"H(tl&ʀKJN%)SwMjHa.@#<VMIGHD!AMA `SNAE#FGH35$"hp%i23R Ԋ&,]NniIRkʽ@K.VB`^~7/^Ww6chefL̚q3vv{hRUiW}qZBۡ=[xG{/R lH@3ܣ)nB9:RPn܅Ba %"@?:r rss{_ꁊK"quśKj0lі @ !w2 Q Ü ipD0P@3h_>ALShC Dx¬ H* b$M AD`4Be'p7 q!n^D(s X 9-2M{4Zc&)V䛛/_7:VLJqy5o~Ίfyo|_PQuّ( cBv t%A(""@"Hwgw>şe1u/CiwWM$o/=479 p C  Q*Άp$Ja[X Ɣn]DBR]yRRbx#hHZͼZࠖ$4SGpw'N}a[uZ-6d/^H+UJ*qyuq])2>~ +VT6)VF5*14&BCᆤ#eGE` 1VD"Zogf%5I*tttJ]B=OOeCWH%0lޏ&I')"hW=DCx3a,q3!jU@ZY|~}jf DX,fsrv6OΞ>:[<{lZm6G}=y^[/N/Ky[vW_ W_E7!2Xm"' J{iP \ n AXlNur*FGc &E [?YN_.Ȫ92%eẎǏV^GvПZ͋onbi$~yu!}TV-{gJb@M "cK! FM!P4hnX@}Y  /5DbV)-y$GD;֪z;d+餸yCB,DBUF5J;6<=(TzHQ%n %"U5Tt{}wj3 BH7a|DBsWal|pll 7A?}9qU{͛b8;='~Wg=,[l硧gaX.e: ݠ=%%а`m0Nn.?#uNz(1Y&RC ˋ]ORBsTwpPDz@].d@2R`CTJjrpg"D)^ka$QbWJUpjB8 Q2*QT݃G ejue[ !aRRbQ0  7'#̫TGFb CŠ8sA(ZøILis>=ϫYY-6I~z^a%PGJQ%Ϫ9x5^ӤVr""m+sf@ FDZ- hrI]>)C9Rg'} llNw{ԕߜ6rrZ-uNΌɜd@``OקgR?DcuFׂi7hte7(IDraM*S-Bi ;S%!*"ݠ)S2R-(PGW @΀UrXD?tyuLUHT#H ʾ.(Gƽ___OvU?nOϞtޭ$alŸZ_7٢)2V0N7ۉ5-2=$~B$U_Y][݈N}aaajaUQ!0McOӝ[CI&Br^kaE$D4b""rرh JXT}44&,0dX *j4&xZjd# 0aKC7B"+-S)QŲ_{}t]N> i2 }~ɻe?^aYnwysq|{˯\^]n̾]ZD9Qry\HH7<}ԯNjbY?3C~/O?钦;?=.$>)Ю;sFcpK3Tvl|hn\whV@eRճ'Zs33"[Yp 7˃aW#!\-ZpXq]|Z.s)-AձTEy`PQl?yu7[F@͐9K7@ q}Xq@ܬ/0UnNYJgJ)!P~\KS0h uQM=ٜ?;Ov?XĖwћMN.TVRBDn&"SԠ{ܻ0\e_A#hc +Pm+Z+@s+$k,DU.1* c2\a%!%`3I!Ő=]EHbD PG=:% 47ҧzhT̔r~~l߼x7m~??zλq!`08_Xhe&f$p ³JK珎r n0֟\,Ճ hgǸ5jyYru;Y{G Hl`Nkcb/S<&iJ7wDRLd$\[oo>K5 =}|e/>ǻ"z$ BQؿo)$/͕EҡN/T7.y8$nnb5`]Ju/`nX /VMçqj1iBroމN5F* ̔mAR#~ 0)W(k2¨=f5ħNeVYvJ%P$>9s=ī9}CPݺnZt=ݬ$wy8#WьBg<Ka{lJ972lH5. +BoETa j ÂB?&GvkBtRj N%$(Ct Ѓ::qE1 mJt_ܧfGJ/w.z Oz=__.:^WG_vbd9VT%i0+Q9ղz "~"(D@jO4)a>012moS $#;A>9Nw%,}0Ѭ 4H !mԈI-@JTg , ;,;K@KC]e Q 7XDd*E?ZꆏsDi#! ; DkZXHƏ qZ]iVPLMH (nAJ{ˬ9;"^2oBZ3 ԧ=[/5u-KVI^Wu#~,s[Ǫ'4 +iS+n}X.ww13œ.C..U.+{lJ׷("<YLn5&{}3U6B;,"&ZJ@f2dc-a0E Xu 1`Q'3T4d''n$Q8]R.!YanpMJ(!B]T̀{L=Qg. Q"R!n(nn-fm K&8}|t)E`[(;` 11*4OA iE l4r(x2sjx& HH *R70dv.@XٷY+h@$4ZŌoWXD:"<0E?Nc=87\xO^^T/ p;`?dSC(p:8yH4{vT۩~= B"HOjvDX9i5w6N`Z ARfS)L#8Nc47RJ"(4qwVTbp^p0'Yu Vswa,AE@PUxDX9"{$G+bLe: &2|*r6B{Xm ΃-9`HPeΎ5*"Z3RZ*L RXc@o؈s)6 B63`6(.Tca eo{YqzH}nsJIj@/nbꮹ?GRWw?n:Oz&6xdF "twyaMv:z,K_iޥ¤#D$e7 b군F) a:LcSe8lG*] NA;w7u*S[0&rʩKI6΀L9u)ҦBQ0@蠇vIE91R@R UKejmhM*rw}y^/g:4Myf @jP5f($..´@RRH~ɂ "Ĩ.}!7isJ- ֚́c4 sftJN9f1w<6Wр qoMbv@EFl hyQ4z'ʿ c''?2ȉ0RPR/cRROWZˤSHIS2@i:dM%2ZY ijY,{A 2 %E҉pGJ&j(*E͊hҼ$s4jvy_Hj1M]4}YJN1I2}2r yq{}qu>4E֘\ pVhe wz]G!D42d-}6A^vgؼ&IƱ&}?`;j<2|~ ?"m{huY)R{j?lu?ԣMxwՊڇi@$54$e$BhHNɪ"(@}tdr`0e8au0ڴz D [Y1Q>!T:u KE\PHគ[pر:6`8^ ه|i1[?⿝*$[;bI2g{sLHH5n)IUS6mdʿ <(Ol![@TFxrrQji^COI:Rß\ Rq;<{~燏={vv*~ }s؍INJ̹V-K֧2N{{漎2d~'#O6@jJY`%Dߘe f27bUu4uCDH-Lޔ]I:3T$w}uJ2j2/krx sZ5Ű:Rd=Já볹kd<4:$( R%i[4#(ɧd8r !:WQ[R`1@BIE%6&ëG0w]$#1ZDp@Efݑ 4! O&`hL&^)hjA렙QT2Фh3AmM6 < c$֐x<^][t%ۚ3Ly[?#,OVv{$u `ҞڿwFԓ㤹~;?}fR3Et9;UMHJuRi7o)K=w7͛}jXΖ;#݋ݬNLb:l`4&$k͹N7pT$D@Q*e.V9 }DZG$IIj]jRZba)iJc;t$5#"as3hkB#39 m 4PCr   o@-Rb;׵# ,Q{FEu:4[j*#:CCxι+5,]x{Б=k[̡}8s4,lЉ =KrIݨ$ƒj wσMUl?~9 <սrx'P(#(: QID@5`z_|Ea{'5=~G! $C!nՀ0+Q,Ja+-qG43M)fXy\Hw!E RJ. $fwrOZ^#<"Di<q/dߩϚR n_nEhzl, Ten8@YT=u ԨL (.uwWXGN{IaP4[۠nDZc8bZBy?Lj 1gSL^fr]~,geR-+$+RH]\|~n7 gZ }FvDUЌaN01o^}W? KVR߼vlb1tU*暟2H'?1ƌƱPXSt"@= sg=maBD9$JhJhFi* OWo^ݼ~nwj)Qz V `!I$4zLN~0D@(G{#4(h*mص+6"H@J, @\PY`'[zC1yR;JR 0̞fѡ3VRN*YzaX45`>fq6ݽ?W\@ x^h} J#QLجuYJdW|b:L@/V| ͣM HaRBչC`R13̶ݼH&,|LM=~ f= d9+aH*"Sپ>ۯ\]s+fQ~sr~99U4lJGhzu~/zryyA P`\*^Am%$)CԐJeRjc5%>0F@n#u*%;zJv$-uKw]~~(c7p$J:>X~tsu($2uئ] Üh2 F I۫o^}W큑s:ݖi׷oo0Nq$H[=Ѱ@L"`K"L` IbMS'M.ٙAF$n>1K=o!1wܛ~ʜ7@ =>PkԤ9(<8Kb)zGO.xI9.;I?]L%n:M{/wz 7KY(XÏs\Ǘ>A}P[ww`-cq5$Xjq~B"I$pk[.Z-MǩTYfpD3K%]oet<7LB0RdHR42  6DBn(h4T_,PeZ,-M\n"OdFCFep AcaPXa .YYָa<2!*Fe ]&C$l,?ͭLhi(f;xZ5Ty}{?}IE6VJ뜔D%Zr9``&IpQ}0=R:@IḮIj@Dh!$anmF%-UR>B DhR̯.n~ɰXkr(#*m F#\ u[Xݡpicoocy4Erp3Z%(S%n#s4 DE4I-"0PC R]]PHP"g:%A|s&#oyx?n)!h<%-yEpXsE&CsPm_)}tԡt8 n;%)|mAFjÝm&T#x9r$EC 7@Y}\-',KI,9kYٽ:W{2/T81G/ {[.MyIFg|$ܛP>) o2܃h|H]RO>y>ތ}g)9r%47֭χʃ@RDB{f`Va:η*aqcYy ӹiVe DmxF @1XD;tTU'0H{(:(K(hd`L$oD׸'SM9[kj2?6)9'hx+!O٪0!W}Lz$QjeaPVɄG|H{^?O 5&9ͧSjop嗟}OVC1u?ч?|}YXؖ0E*@S"F#YgJ3JӜ\wF4y ϱ KQ9~Mx1$)Z]5-_>Kh,iW˝q9XEuEYX*H6В")]QIcV!o9<6+Bȼg'"Z/YA/X^ؽڿ^3:[1= *`CG3--0% g8s5 >Wn/dq)\[% JqZN}|uôkeqRS6bQPD ^k\87f]2 d?=}'ϟ$dMAQʖ&œI&8cNH|$ wD%g9T?xL1MೲL ֆ"" I9/2 ڝ`L&D r߈8A.tk6.6gG]kpG`zя7''UzTwvQB8ޤg$HÔ"?;^2ZBpp9T`ˋ]s]7g'f}.A&3)`mC%9]$ݨQ)/PX@v6ۼl,Q7T%iD[hRA8zn5 \PU6fgWwZ?N|Z^ɛr+G8}@7@J@cglj"ao5t_GLfdҏ kAIcâ(ŀQ^xx[jvs мn_n.cڣj͝.ש?nMR2͓tLwq1mB@ v:.6y8; GAU% 5FM~4h{]E|ɿydYpx-aXnrݏ5tOY8acH^7&B M#z֎ flo,Y:IHNכ%U K#(X+Œv5X nnq䍷E=B*hup/-D.[fР湻".)uCzrym_^m_/.~cl_|vZy4אц;17>4oN8s0LA!8J8Nu?Y)P-rҡ(Q'^aS͋,Nf љE-7ja4:^uԠ_MXtY:,G~FzJH!3/2mtcSQv3,ƣ"ܼI!} \^|q7=_oGhNmu=|Ս$ 82Z֔C%[ҊOH87/sNm՚jx}<ɢ!>2TS o‘! :GZx9LmZ v˰U&fXC"R9 Z[DTToh$LOxw˟?~{z̞_Ň;naV6ꏠ[aw!z6c"o ]l8MEׯ/oC.W]~uvJ!nZJ)pn9jggi5VLc]R$ɢle,uH紿C~yy_>.q!6v.9@7&_F==ϒ@HIwrXr~Ɖ!NH2/YM*^o_'J2NXa?(^x>Md noYVSJC~Ӆ#[MR,$$Te =jAJ2  #r[Oy4xR" w0^zCN@7;#~p7|%:8}ww]'}>ڝQMsmg߇;%$u}x7~3ayrt98>`_aN j+YX'Gq{M!Wo|#94ЙVA H@h+b:XD!hxbh;64v{9q9ONN?7~"}SN؜}!0^ݸ2t 6> vu"y0yj|}8ZwwׇiRNն/iN0Dx0a҃.O ww>Z)|Xo/_P,-,i#w Ɨ͋|'h糏S.N#О"B`@Ҫ VhadT#0/>w/>U%IFe:I(f 9㶎ew S)4p[/@_0 Gׯ"/dBЩzB(U#A9*fxlęRsxy,&%5moܣz(Ek0BD_n"Bs<N7ױU?'{b7X)bׅԆa5+ `x2nR87K)w՛۫7ӮTLJڧS~s`<*aiNc_c hʩRNYXQP^ =3X d\9SwNV뗿8/ϟ ';XqzR5H0 Ca%9oq&QF%I \mV,/_jZX͝]9#D=pȺKy+#poׇ1/$w"'Tr>{:"M/+"@! [k?>}9s{Y۩ј%]=Z3:2aMU.C tL-8wdǃAp}ɿETLXwwZnV3_w7W˓y&`ni}fL{Z9Rt{ݫ|t77~蓟nBR ա;X 3ڔA=B ܐHsNr=;\S^eDŽ4;M0Xb=!%0cy[`lXݼ?Mw^^{E+"yNFtmR+B!#yo/)J̯Yh:1H*򼜞-ɡC(Q Ym@aܪ^4e=ۚ68__{.ؘ&dׯ]oGh)Ӌ7={͛kÁ.!QCЩȲbVGX ן bPxۖMA.Չ̽6AuMu,7@dMg"vy=,j)ʰZިF(UmnD|yzoOׁAXuy4E"!;磻}Ea~gO v$e`q`Sɰŀ0D/X@z˫>dH!gϞbo cb<y z7ob9o$Őg1 =D=1)D"KG 6Mk&ߒzۦvnX JNK;=4MVjT~o?O8*q3``w7~p_.뗗ߖqF?\NH^fqzZyy\n`, Xxk7 1Cq3ɢq?^o^J.?Mt_e4-XetYy},e;XJwkATww}fVL(aETR@P(W?lwe) ]{/믝'g:;UBWS-)-5}Su5PG EWz/z{ɫ9jN=}೜ \wdEJ;{[;Rc}!ܕE4tiy^')RV E.j vb՟?\v uoojh^B0]Ⓩ?oMv{Ytր^!.wZ&MXOVCNjџty$QFm%|DG8:PPbz}WƻۛKŇ?})\S܎fvwgIDRԚvW.)!ױMwf$tƜ/7g?d89[v6%2eYyq~xç|pr!]yI$"MVZ7$iˀ{nl's:_zzK%#$ٻUVĽ[_kp-2\fޘ8#gGDGh>ZyY=췇jln I o_^W0 '}WFU5OO}@l}aҥ܉h="=$QjiJhim ?ĸWXl2r۪EχǏOh馌qyJs7Fߗft*j8x?=ŲIKj%{Hv9w?RYZpj{{o^a$e2$dK(Rr{ճO,nᇑџUJg޾Kn%L.}U/"b)Ξ<)9=|p3cg#{IO'}sCf#>&q$85<*Di-c%в*6?W訩\U⨩_M1:zl'F߹zoyrW?뻋#ݰ''yտ:>>XnN5mrH hw%[V=ݛ勮O2x;IXNbwQh >3|iI!KPuV#bUK.3G԰*OZGֈ~&IBM"Reft@,~^nfw5Xϟ?~2'Aq* ܻA6V׷oT_NyT9OXN>ySj[^w><,Vwe}?Y ld/G5Ymt?,|gC' Ϭyio(m0 (qLQ^\_<{#臼:;i,:::Y4gpBÛ/?r7v>K)ibz1Nfs]gnfm pR秉Vϙ djYv}u1i<.]O'OAE`WoDλNk=Z9H)OjnelTx_o~u:]wq^tчw]{.N)őhh}*]h/teX,~stxxZwoF闿^|m.!nX,V ̩->Sš;̈́S{wD=_2&u[yyKjw"-J,hvH O?8m/^~W_zw< B`^rw(iSijjx䝏6gRҋW+S7,{=}|=z%B$Ԏ]˻חZ}vnӿyy{q?JFs)NyGâ'u:$/#JÁjy؏.zj;!!zT&T\$!$Sz۫gOۯ>i^v>/V#- cx:ﶇ_-;zwQjO~Slo^aPCV$ yp',dJ|Rm$3䳟zzE¼D8wmȽ WjMXǓ~cOWGc{FӀpNw*z6lWBjʂ&p~>+)a* b>Nm S5ML 1j#p(r*[n6g>:^ݛ[>Xv~^#fG|8g֭k//jf}uvg3{>fF|1s{[31FEbG8$Mkns}9]|y}}܇zY቉twO`|&"%cyΏ]x4>1a}z7VEq<ڢ~?QZ/@ 7x o.u bӧgO`1!!*NGDfJ5 $:tWwog>Jʱq/?N~o_~ g'?.y /,06JntH)ޙR7 V+Qëaa)-Es;'uF<vחM  Xl]v?աNg'CެƘ|r{8VuZ}n_7DTzٻXO J0ͬ25;|ۑɡ8)9c{B%=E< [sg/_}綿RXDC/bJpѤV`g?wxu̒` H5~wqz'o֋a]椢ZƲ (nG0_T g~iXx"0 3@|*VODzN!pQmL6/J侯w>։*y:]èiyvvݛnjn/Z%yd[2u9"+UбGn0Mu)%_ZM.$Q"琌d.֖tkl#Tռ.\v9nnƋ>.ޜ,v; `{kcAbW>%F` +tpo,cJE\y19ZmRvHcG~a,y } 7J1x#m1&A+ͻnb+w^1461D,vWZ 8(!_mZ!nx*nX{v''H_}"k:oX-zWmpSL6ONro6]q BVnl[(0(SBiM(g\2޲Ij,-WTYC1Lϟyڬv륫ݕb/>E߯}7(͛Wׯ_ ?l!4]5"v{D 1nrծK Kt+UY4Ι-zR[&Ju,FJ|"rWOƝ>KuDcM_|o[' ,-& Uvy tGo}MY(8淴FwO8{Z]6__7_gNE%Z wF7'"L4Gŧo^y_i._~^</y;0GH)5ɻ٧|@Og܋Ic)\Y8`p ݡ|'Op^X=u+tRnlHGTb/vڦF $V7gIEgi:1; ݤqde.gA&_JpZLݸ kj{H'ª%&gk+]^(n +$)t7쁮h۽5(e}J^kݍ0 }*T;˶Z2^/ .74;{ϓgE 8Q0'őPwZ]r~]. | %H_˯>͉S$8N=MeHѡQSlSej,۫WOrq8<~>pP(i*ܟv?O||͟nA/pݽn'I4,$/!qKI )gv߼0,TtGaVbAicqX.Qב@klu>{/W//^^_4_=}nq:oox24lXt]jnZ.\_ƺX]Ξ.>N"C6TClSMyLP @J7pNz8?{vw;-d-Tu/%w]Nq -KMJ:(4Z}:,gOF` 6є1dtbn]hrX|H{?V 9=6<:z"#c޾_t?moȝ(1LnᎡGc="%hG" 3T}G `qmn>{RkIm6Itɮ.PGݬ?N/ptyƮv; cCQ'OEUږ'%v/=L,V,K޵g9"ʬʬf7&)2% Hd}c X׆ol-CٔQ&CU<sk-_|̖"p }^cdڦC߀]ZJDgC9Vyq!8׎4B{7Kr\̢ۤ/{?.մ{/ǚ.Ɯ|fx _m۪2l*^ )ȶn)5W BCdǪ6Cj^@JI|0޳W1|u[XBz(JyUU9mR kGlf ˡn1"uSC +(h\Ok_hnUb'##z^*:S`>.V|;gO*8ۦһl}ޱ͙0*AG8 dX0 %+Y,{4 eIOќ^h즇zw۶OrVk7!*"62 4=6Ä~򳇷u:}]5V30A.L-'.vKPF\Yzr:׾7]0(&Kn֫fyyͼjّqqqrޝ7?|r}h4ˏ?z|RNME`L3Sks6oRګz4@]Q\-'V -y W:Y-5)l9=dFqciڞDȜ??;}hL9]kn߼;h;%[<#)xXpf⻭}ō|%OEٿ>#v]]+ԟPҧxq>ߙ\xӾ=?} v?Q|P^pclAQc\RQkUn6>*BLjAG2':c|1sM2V]&VM"HvI5rr \D,+iMnfb)bv>xE`OId9/||j#.%6dZy簃6]hyrw(@z&` Ϭ?oYs*:"ȼw?=Fe}Y?.MY19Hc4CeJ* DߊeիW3$ŨDVT%֙\TdWIҭVM!0{byN2aӡ,H 7UZMG&Ťo.G[w?__]6.kxDžvr^.ɀt$m'O0J6FͻPbTM_Ưz Vv{yijٶmLr] FK(U#Vb $9JI4J|5v9+wpF_Gn#6>@d3^a dͳxwx ޗGscr^rdGc%HH9&<ʾ**֧vۯWmLqzzVWѭ{u)!f8g&DޣلUw΅@8˿7xʅl\Ξ,/buUbL x5єomJ5"lCsp\I\U8u٣>DR%HFzH&q<]ODjs uT(!^J[vϋSM-lI4_NlQ@R m\Ja_s~zg]H#8(By7 HE.:, p0% BTVTLGϿߟo|z:)gg˥'O϶ۼm49ER a sO~4.n||⼟ܨoM_)ʊA-G٢o7Ϟ>{M~WbQ7Ln4YD[S7\Ƈˋ`kpuqD}rڵ.O ƮJ۴0ͫEL](#]fU 3 j 1Ǩ]8'xrn^+ff-+;#"cRclV"e;E@`6}zX7`O%'wϒM*tZ92ma~j&vYqa + RE,DSH(ͤ׸nΚhTKv؎ kR(I:ϋgҎR?4>/N=y!Yr\^۟̕bJגw{UA<|V8a'Ί>VO1 oN-0F{jhr/Mo vgϦ7s梚fɇ7cqp jsyO<8DTtqvl$~ptT޽wz?Fdչ'gq؛GlKgȕqszyc7^=7jVxEe_^;|J6m{!f%*iͨ*LD.We >)eM*1 R/yW^{-Gz2 ;2MҿsVڐTyw-Urb̀,j=ѐ+gp1T&ч\BbP} *`9ϒ<٬E͔]o؛ J@ͦ<{kJr1M]u_oj?t>W"=hc^c/-y.% r^W̭7'9LGYa'8QRV_`^i;ͭ'U;6}".PioHSvT]^]\4<ߴmw7md[w^ &a5$! CTLS텺d`481tD0ll1z&oäB5E@4hSco;ne<ၹ[bDzpΌ$2L6'_x\j<\aKx%5?i!/^=TC V8&A+h6Q%$C&٪HW߈V=Ihm4>_s׿TO͢9GTqkӞL).ff:U٨fӽb<hBC}>u;6 ͖eg0j 5NO6ߛ|8%&S@̬Ӕ0mU#jlV9y鋪֔'*A`9\ @$ L)f$^RgVfTx^.F%/q>\ۿ;~_N9v%Τfx7(U*sԬj4M."!2"Jv2 /d#λ^4;Ast%FK=i2P,3B&ؤa(8Pmo[e]4ȒFSĘs^toV*=fVNHt~$ *ʁev)wDيĔ{xngϟ<jJ,1`Kd@$cmClF qIwno7\w1˿+hE2_J.kQ٭/obj;Q/F\\O)r]TU?~xv~7MߟÖJڠ`ٝ^ރЇ?k2ΫDLІ ]ج܄!.vYf9 xqʮ,C !Pէ"rWeVoKWDzPFj6X֭0W@ x5 T*s-3 0D>30Hw셧}Dd17 FvpΩf2aAO]^l`ܙlTM.|{Vm[8לMH~yr/) |&gr/(H28DH/cL71v$ʖf~~$?" Pct=xV5`AYns `]hPA[IҧRd/.7nAOfO,?.+wxt岛Le) 8Jfm(zMw*QC s%F(=߼wp_FI>1}f"dMKgiK`٦màp~PwifBGNݒ$lnhd긘Ȉ▣Ev=ngA9B2R5gC2H*1y,-w?'zCR]n쾳;<ӎ Q8["EI6M5&G{h"Ё9;@ dSV0DLJ?/5$l Lh׮_܋*!~y4.@JR15WeuȔPtoQN,>v1+k A]PG s b bR^^t}9S6BzO-ƶ]퉣.nܶg'M;8{Bv|.Y(k(By/8u??;qQ3+B 3T4o5`j u>PK|Y$xS,;WxQibrqK}4xJr<*h<ƄJwS99&ptxL vT%g`~AAHҸ(B40HHri ͒&%4;@HKT3WF DB֨16NzM;Š/u@/&?|ӱQdJ0XVG rn=ѲI &L'ᬚI }@ 00ZRtV~/|\/d߸vuֵ;EY|}OIUVbtt+Ibq|#n79#FFzqZV>mSJ!0B?f%F0#nsM EmB%41؇Syz4Qڷ)g@i[Ř5gFPN:xY] *<FO=]]e|.WJ}(ĈP2vdYv1(U'r찧+?<}Ak}>MchwL@sT?/D;X) r.16-r/F9 adUqѯ.FST#1TLdHU1fRf ѠOWz1 f:0Grs_|"l,w+|HKfb<v(T|a"$-R5_Fݸ` ` ރH()^vA3n('[9/mR5A.z2r{ۯ}H5JĄdx2Sp٤iESMJain>眪37qYaӭWh@; T(vvGgz 9EDD 2awzJS#X"SI4PʟCzCN}o)#0c'Spҿ_y@JFLDFl[ DFtxc7DJ esdP|@N0(ᇆ*] ٰ:J)Y5Ea\T%7ˋÛ}jWgf{y֫PۦO}>;=5pQG%Ixi2.vba09W 0IbWė`&p`ҏT3Zʎ.gh5'Yh:s&rx^m§6r4—M4=g|C3863K2hͻխhf9.Q :*,&3N5ZUVW>&9$>h9Ç,2!q1 S%PVR.S+/-\:22@nJ7"9F.Ǯ舳w #1HIDATS҅(EͦwE?~-j͖MMro]>~{{gǯP9TRS\LjҎ C(֋pOoO>%H'N䣤Qſ6{C#R%2S^ˋo/i1Ǩ僽kýG>Gro_k.zyw6JH TG l;Q+ވ)cJ Ts32V㺲IdV==z 7EY*|%_'s6zcmwtt"}Ӧfv#G<}nԧFĞH]n>\}A9!` C L]R"TY&>',9mbym;&oЧY@ $B *4ib,G9E"?{NnH떼*}YQuqJ*9T.v)T~靾_9/OR}{W_su?}]D68*FnILPE4E9E^_Gٟ]cvk5$^ߴl}2k7uSQVŚQ4EY&jY5$*}n6mbܮW'O 9{J}b[_N»A&V.-p%~>2!6]x $/[mOl}fkjʂQרDjpZ}_`F|\x6uླe``{T*dH3 ?;Ӕ[-ׅٳGO?;sģ*!ryO@PX^޺udz`.aG}MwA 8٬^_(fIa)ök6ߞ^+_vu+ٛw_}j[LfdvѕP@,1 \e\E*p^ .z@ޞ8+@9Fz 7D9$6L@4YZ΍%@RUzZ}r<|Ѽٟ_߬/U+³+}&=ףIvO#lyT߼o]%Lv2R'3<8o~3E Ir°lIz/?i[ $6,K1blS*"WϙAB g(mG]2) h߇M)"sMs7Xl.FIK]87J OO`wbxwh,8B@!!cwj*ywK(*@AyCllUb{Pb٬}$.l/6͹*}TOs޸y[nMQIݧWxMDDD ! WDk> f]}-tx Ç0茇\c7|kؕUP`h5fSS56Pc6r8!:aqq|vdYz2:KȺ0) =֦cT.,yLw*Րn/IF&M_uVW^w|ƻ?Ql7l~(b4+Bݭ.b,FժPM ,_N}9Bxq/Ѥbw҆rUP{*f.ƌboYəB"~~ٳeZ_WG>)6zqfQv1o杽c͏B]];8 I9"R D#u>1Ɣ a>CK18?c'wG~Av7Q=]yr.0,n0sd\^nn겗9RڈvveZ"+#wp Ƭ}3J<}G 0"Be.ǵϹW爸޾/]G0IyP;=L`Bd׵sH 9fܹŢ,& ҄@΁e} ؗ$6k7n|g?;:oEwA3$.X<%l=( D°!^L~vY!Lvw1*ABT;N𢃶]Z  + iΪYcWU+hgrރՔȋP ^%Wmwb(|H3cI4Ushk0LA\=R=4jsM7Gxcd[ e{ək'59Ģ]>O:JP1Hc;닳sQ誓gMPxCqfv!x!*XC3g7\DOB4sD2eVqR?}}߻yk|z1ceBA 1EC.E iˑg%r !dYxj5>80PGBDYobkd \3_ix>BO~lPi(wAxX y`ml'p@2GJ ovl h`:B epfSl3(8+*$ZwOY-eY,(KyW؇U%F<3W5dE5]b4i, G}^&&Si x9D"ȓ017+_6En1bw`BY#($oMbʚ1n"L(y99X4!X@QqP 3Uf7tKs]&:umQ{X=oVEdoZᵶ_ο.Vf'I͈PW`$T= 9z@9sz`P|2I1?oz ۵&foI/\sg:s`NC  Al2RGL'dys~r@yCWiTʴ߸]ߺu%Ғ*(ޜb&j9LqAx1:em.Q!!3 19]bt7&;߮]$v4>xHJwҾTPS0TU5pٓs0S!y)( =sÏD (LMt2I/5M2wuѿWN͏2UvDL&L M^pPn\m^W *D!%fD0(2]k6wKw~aM^D&j"n/OsXm~pm4sC`OxAYhcC91TwRn"Q$wC+ (@a[MG}j&]_^FC88 # D<{S8jD4z?x^E10a .VxD(0cdmn:oӳ[?Z_~W͛U`SؑSS‘i7 PqQ Wc0|1xvLg3qfm^{- D ]Q?ǎڦM'MŮLI8gj9b۷՜jpҞ^~؁Oè f\TKsq\/&4:vUjj&EQ* LLzfGoޡLn`qBwl RIOKfS X+گAʛ\yg!ņM` DP .Er]1-o~2mC҇GQh[el#Jrpx8;?Z,.rZ[wƇe(+9"sزP{E)@Q. \Hgv̎ 9X̬vT!#?y_$2DD(j(jiԓdڵ/ fd4fEصvWx ?DNedL0ݹU ԁ@;dnZ"?HO90e,:8%} st>T>+ȲN)Me/ݽ{ͿM72IFG#bs(*r„f=_ORT\Ŕ"3#rES+Ff' \; R D&Z)@ ` ]`lhtVU/S3bb7}W5ÔdI!E]k]~銎4Im.</Ҳos7{xx!\GdNչ}E/NN,Ϩu_=½cB"Njٍjњxfr,`ƎU$;Q0!Q*ޗd a ,VUeL@1fӉw/8wP ]6hAa|οR3+qg*߻ \$ /L 0S3O{'Ͽ_KnYVIZjMDiT(DF,G>n䕛1gW쉜QIn̮r~d~8?{@DK5$pQyGԇ*xOBQaj tPEF\\]ο gsL ftj zk^}+\JEY{v%%Lz d63dS1j&"K2BjIXPٍfL4OGv@bwqn?{tNo9;";w_>[MlF4[6&S5bbfG$Hz9_8gD*>vQ9f,}1jLI>Qbc9Zm\ލo);;!0^F<;;[B]I U'=/^g@Gd-cXx [a@n&d HOY''XnS}iolMT$i6UϞ^JLvMnڤrշqt+ر3tEjJc<[o~ˆgs]-}`Ȕ9WHP'r+>>Wwu aԃI  3\f f 9o}1bW9 .t+Dlh /\ޮrۉsqV28ֵfR(QΒ O|47W`+X4D/qsoo񄂡݈u zLw<|vmG䌨,DF̐ƌ`QYJMv]J)ScV5N9i91ɳo꟣Py.a5ټ;2^ûv(i!:`fQ }Ԉd% $d$p..gYIx~8o)ig؇'tCfR%MFwH3/k%+T,_1WcJe>.-B΁K]Qذw0هn~AK[3cEA#+(H:4Mҝ(ƾPbML\4f #LL rmSNbIMET.db;'R^m__\Ƈ[s:Q(l(珷]>J?7~>z[Kg JΉ y1> )e$Ó"I9g*&ռgv>('J8MY]7s<"&3.yFOWeCN(zU@fL4ӎt;I-3 [I~ryi9Ů[=O.ܧ_(ß4m|h K6iUG!r|/yw~r;'~zzygryQ[;zF5}Q)KIKȑcreB{?LW}' [t:yC,fqu:SWק"+ \a~`  49#e_ 琙&L0aLRNZV"gfLhJIM'9piIH"SLl9i"e `ӱrṘLukm,"ӣrαӊ\|x뛿ⷃ+ʉO{{|ƭ0OITs~׍^}{,'z;?>#X.ö{ݦ ǥ1Y YP=*#x4x}Y_kO/''\MGE8ݫ&"kl"Pyv+ɁH4׼QFnzN:-}0.ޑweAat4ab͋ۓOضDPe Ss{2\VX;C0h =.CpDE10l2@::Y2s Jf2' 昉J5Ӱ^1&YL5Ѡ#RN{3U3jSh#jart8={7u1t)³Y"")ʬ;|W~q6α\1PM* &} y'?;|KY3Sda)tH)`ZbDEja_-ꕯ}ػf=톞 ݉vԣW̬DTyf&&agKů)@)em[ٙ]r'^3Ï6|,f }?EPd~7nޜ]R_>ygnlܘk&IM0ds hLΑ6..4;fgI聶UHS{~ hLNeϗ}Zݜ;wdC6D.f"9xsŽw;! ؁K [F(fdj=xBJ(hSأ,a6b4K,nK9g~0|.&B ML%{*i20+קG;þوyWVURBbǮh6+WgTň}qƗCgJV#A$hM=(7emT,,!9ͩhJp*50Wz}̘H~%2 Lk+ِaQҡR#VR 4݈a{bOYNX<;жtSt;?ȫ.yuKgگ\?Oo/_siAޞ?iɬcZΌХ!\OOѾF~Y%v0u2yS2c.>;ꛋM5.{@ֹNbIjfc+xf+ ց"1>\ЫPZ!T%%t)5"yO,@7YcD)ENjח*څrezIRϖ<ፗn5 V51;I}N&·Ե[fPT\pH-TI jჺLR꼫@.%X$ƇlVh,3̀IV\0 ?\*f5t0 V%* 5#V*#h9gb8e2CiU4^}>_>'j;|-//S'nwNH t矆88G_x5ݬĖ/zڗlml>ol~}կF%4hr4M8Gm|Ȳvl vfB 0r9ff]h˾%TAs_h.NBHFIt)}y~? MNjҦ{i(6MW T.~Gӯu45:Û q O$Q:!HS5(e)߶H5{9rD)K7n^{Ku_FP4f}^Vcɉ| ifI0Hi+zI@HIj*z~bf$mj`|4;qjfa9:f20(sUE;罫&c?Eq/M{_d`@4 b&fxqaW^aeBDsn蘠dp&`LoB]%4«w~ 4.}ٞZdfM.qگ:l7k.ˏapQ[>y3gNϟ_4G?z~$:^6`짳PC. MGߊϖ=z&S/Y"ʹmkQo}H{q]Vبs@T7}\R: Č/Q?,` {۟HE{\c#3Qǥ$bP#.vXF%s9X&FPM}J3;Ǫ_yu5y,&2SHZn/oe](L\\^qȡ:.&s9{֜w;[2d~G>ŬF_(ק?*(+?yoꍭIpnTt1i"ʘ@ R=GQ cA CR)3{jFNHbE9FUC}q8-V =3IN&Ƴvwޠ١@q59z5ˋܝGRQ6ePhvY^|NPNG>2P_*ԹP88Gfbtd{rףY=Gޤb2qŌ]Mb1k;bx;ŏ!L] `°55ʮ@oMf5# t0J/?5ov۬6nJ&^Vezt'#Q!d7 }˯}O>ُ: ePd\9W0vTBnMƮH̀9 ɍ;no\nMȱ/kzr"`. goR\U f\9>}T-mKa`V_(ƾx]5Pʦqݧ乐ꑟHh=ٯV/R|P6)1u}E[`>8ɍH $t 38vCxP4çr.urҘb׉NQ4Oo~|-SUeW}>~Ͼ=ĒoM|qM߼ί_;qf  q6%rjjvK[Z΅}O7hrQEZ^4I+R:m:ʊk.1n>FmsΖ[+evަe\llmmg٤7k hGBr{@8^I\K@aeќc]7X,fC.k#23 !X_o|u\qyͺ%b()zl9'2 E,N|`|t3a슱8}Sj.NggCD~Q<]G(vڟw/˟o>*^6X)`l;ôf1B` \L/jgP lW9Ut+>hbuߛ,fi\rN5g6+UNݹ=-Qڥ`oݸu Ѻ};/f=^cd7nQWv8n~'Ж\QCd<Y lDⓧqݗ'Zs.}z^OnܻK72?qt2?D](,+_$Y92j"Sݞd׻Aon\NeLvZ1bdRw9,<mt6v\ ˭GQS\-ÝKzw؇nCҥ$D Yv#Ǩ>`yoQ18wzk/D$; |E /Zr~>j ~[ÂtoPO=k,H>)S4w>̺woɉG.i>sS%rnVgڪ{kM./6ƳI6}NFլ1>=eZrf/C3V)6FP @xeyFV1W_ɟ?ÉUd kĘF25 |J6U&7lgc볖ͭZxR 'ޔy8ZD')!"6,D!$:0RF\ L{w-P4ȍ4'7I59G6;'rNJdw;F&/d O-BT^*\8 '\uUtRdXCL iEYlrpDgq_廀؎Ҕ 8qc5_ nN7;Ye B?lo"2rJ7sGYY|YCm'II Ɏ|loE l}QBOE). }t@jJulqu+,e|&7>Sy5tipo4|BM|"r1I߷A?Snп$z JUWDQqlpTdcf}_ P )V>ӷI*RV).fY',˩@(ʷy P&kkpb`D9 sHFiERmU6($ #* MdVuugwE9T41^E3~,@ R\<]#<՟馼kctG(3@JD %1ItʨUfT٬fbmf|ޝ,cMsݲЀFq+ jZ<~/j;®*WFV"'$n.d[ i08>5)zB)ZD.4; O:xQ=en~tFJ4\Л?BH654خJ!5+_3vuEZ-Bƶ{{>~nggkT$UJ B6|ל &"duVm]"V%D/]ζ/M3q}yzZ;S|]lgL[;2tQ  I>+hKY|lY:Ju_ϨmOyK!HODlD & `DQZKJ!$Dg)%0'D߮7́P[PD2*_2J|bTk:)ˁp~)5Y[Ţ>]{J]ԫ8E6WEo)^eYlX7YarR*~s@Pܼ16EFu6{[Om_6lCm<66vLg(_-EgH/)AJ}2WuBxh\7iͨ"((a}恋_^4`詭#G߀]Ꮚ?S}cX C]l"GP&vO.,wmXV͇Ol75a@T&,%cQH ԖP7x( JTgzCPdSBJ&_b>]7?A![> <ڨ~_B}q:! 'N!G]!j  NT {Z{K—nzUe=ګ_;}noFD?`ISmFH">Rd"^YE.>h@D jZC6}9K- %hBlkM%0 ֧s6I%jyvllqpTOfaٻ2ĐBʲ,닰O #1ej)Qm]:8'snMٻy+e0Wn<{UGTټwPRſ8%$BΧJmelTr%53y[iM^@mi K" S}`')JP SIẗ$!1DN) #rFp vmU-٬mtgc F@U$Oz7;8# $YAkmRtbI* He'cP%2YJw# |AQ_I4Nw7ۡN(J R" X1(PAZO]l! EK֞֔$tᡀ h-~Ϋ=#C˷ok\\?̭=o>LCH>l74q4,:6Gu3B bQrxVԝ?;y֓MFН8z^&u7Fҍœ&Uݳ'd~皺ŕKۨaa:{կ~ꮱYϷRd7?893\l+Bз2Bm˳z/GY{EpR0" JAe檙ρL⤴ZQ ^M*d! Rjqw CYB蚺NmL η`{ uN>?uca@hG{BJ`攒Y/ybcsQC1J&dj Se;F FgW^_(r)i4,6ƓJ 9uȚ(hWSry9dvy`ǘH+:h"c?/x=D$HZp=Wpa\ "zԎrЩnW)- !涃ZmkuncXI=v,U[t,AŝMי)!{Z\Mfou1dʘxb#Oz5M"p޻Eo r)E|WUX t-&v6oh=:؛V9&{Osju􆣢ߏ!8x}5׹jPETD)1$DZ)ő㺒~ 'AW71|3ޮ2 {~q웿ߌtɩiO1!Qh*2_D$|Pb1V%r 1 3H-0jv5=:i] 2Ռ "phSrKe'+e211̬S@BBDQ]Da T ?Tu[59ZݎOAӟ?~xm,(EZRL HVl'sm3{q<~P I,ȦU`7GU=7e?[^?xӟ߿[x XiQ .ݸ@tX2!6 tK'v^ ]NO~0yx1p ~9_.Wu* 3h_g>Nx\LGd9yxy .z 8vNO )S1Egh2DffmL f&"qR!Rpjn -j&ϟ^-у;w .Y~qRQ^͎;=:R;wWy0if$yíkvuX +0K?O}M^ ;ߎ]hHr%Σq;:į@l)/箪nyVB :J !0'"ݐSnƑ%jƘQY(j'*[tz*/@8@)e5㣻âYvq)G`D\U`hn|pl^ڲk;Ziba%h!&Җ45ʘ mB@DPh:Ḱ udNu>zx ׹6M>K2f$+-7 _ܾɷ^kjС< A"P ʾ~XxgҬɝkGo=^B\۞>gyt٭py|Y-bc}15YuJ:~t_gUgcvBꗝiw11ڣs |͝g$.hJ@ " () Z EPƄJnXNmNFN qgJ3>}PdR3_߃G!Е1 I"2`+_-&gEI1,=~P3="R Į"iQf22ʘ1hu%hmœFL)&(C\XZכuk.LOO9~J|ϟޒ? z(Sc{eo?iTK[ V{`;?sv?=i1EL"Pybb؈K'o_ L{ôz2VaU1b H@La]Y8|_.jĥgWt?'ST*RlGϗӹ2;ΕnEZvn:tz n4)]DO!j QbtZWbftx_fFQ} BuhQo,ߺ/$ +!*h HEBh#2VBTJc!plqA굫O{اޞuDyxvkNA%,,u?gxNMl$Dc2Rtx3=gsr!uɯ3 Ɋ/zݰ T !!9ħ_{[?Jiv HUT ŚFX!- #`F$zbx|oWMkP!; : rhJ ^E%뢃"!$.C M&6_^ح.:M)Fl^*%[!18m5Lj%$L)FIӦS̚<_4[KȧƊPfdDҚm[/<}'r;?{r?%yq9Gʲv !)`^ͦL{zZ$(*NfT FBjū84J)aCmZqiTnR6Z,+^_zNf0BLd9iƦ9|>#?]XF+.?Us2Bm˅ Փ'j9yNU\H%Nf>1YerY-m+ešLX)~5[Nj%HS@ @%)m5=']pME@؏%x_oA$A`֯ ^.PpGIY^zكͭ0LOLf ibjF7:YcDǶhkOcgN sbd6q#' Qb.~ڷ._LBQP1 6yV%`jt NNB!-&48GU ͭowwo?ȤbFh?9tR")!kaO z j||^mc1!I"v!U_e!Z '"9YJ(16DYY\S''.oO*}b\ ;bS^dh66QfgcZMT'\g8Itl-@\]&ںrn8|Ї`]P N`eQyލ>?w:6˔*RJ礵ҹ26!Q!re5/ 㛧m rX\lzƬ$k?E9 OlS֫T`a'{O2Ivrx,jF(4EP*)(Dm/a3bJEޝ!zQ/δ$HS]ڽG7&WuPaL.Ppz\Atӳ9x3_@J{]ee k;?~{6NHUC NtT}҈/2i#δN:0t>Ә~f{$M:?b2Q|3~bw^݇\-tN@YN@:E` Դ0Kgb})Mھ͡ STm\D čg*@ MeW$?rsRS4Y6; Ѩ!n /޿1r@I{ߨC-reBF]kIbt*I SeË>>=W~?q?LDWsK1xza(L$#)*~p38_/\%r{8}2]WA EڲYZEp6-3[U QSheTJmXA Be)v7{~emZ"EЀZmWƵ:3jU.l{7mSpH lf(¤>8L6ڱ}qiN&ղlrLm>llj  FDB!׎JTRFc('|;\zc<_RQ._1d @ۏǿԭ*0qscތ!u5P_eҕK;[Picjyr H#ɘY6j68w(;sNo2jyw@0Ajbk-iKn QHl6Ӏ5 ͠6JeXלX18q3n|ħL@\W[;z>_L/p$(x,Bg ̗ǣne^O_ZNΎN$ ώH()~IbHE.VLZ)DDŽDh26J11'؞N4b/_}soג*RjbTѽ/dÇڪ &bX|:k*^,&@M'İ1}+7ţӇa'^9 ?pZ/Y0'AXD ("Tfw[ӟ<'B::2Mt}vEL ɒKPߦ; 9{d :hfcx®-=ȼIb"j&eKeO}!k#'`yho,:@JXѨ"l2cEk,&ST- P1h8~rf:뒂Z16`%!Ph)6)9tjNoJ=G{@\7b&A ħ` f \GAQ@gz֨#Rdj~wJ&'!NW l~m݄$zՐF34wlYCIdcT:^ 5[Slu՛Lؓp(}esi56 )llgL]ۺTj3 #D9DWzGgS=ok %a z~ѝRW,JD(*f.28jw!֞~+ݾ]Y˽oYl~)٠ҙRN%cLuc9/Q^ܺ-֓~lݫG-G(m{Alݽ\2!I0RJXekXRk(HRT)?!kCQ1% +#F18>F?פ䵱&+,Wm)/R9ѹ(@H]f`ׅgvvkKdJO' ($IםÑ0ʅ"M(wot\Z>\[e$u /z,WM9X/G%k)AHRShe^ kE U-rXmI D-)Wbڃ8W %T;[]Dn[]L!YB,KQ{!˷nA|Hp7%V(6.=>{(:fh9%͒Ȳۭ< Ͼy~t4-Q}T~< yE?[1A7+brM`lV IA<2rA$G+N%RՒXT,\eR=$#@*RYJ,?~nM L2ϳkYɈ_f",43 3"LQ%ƐaĜWF+#s`E&uF:Ƹ}4֑J/SbEsmu2u!^W@Z#z W~gXTe:kv6ԝ^狣xo4Gk:!1GI\F63%qʱdBІo G8#֨cBu9I, F ~ L|DCSjĐ[V#ǔ]SUz@̮ojY׀mF jkw2hS @$Ւ*^"ey=7u!Υ>,ް[{ӇgØj*p-Ond62dWuHB3V=  O|vl] Yͤt\߻|xk|BQ9ItYw(ɷS$Z,׽9A*+0&ZUI ujn)K) IY˭#!/NWbH1L*hUG|͎1v13ʎ"c(]ԺpJa??R[?Mz^,>j=Goħ O SJ Fkgg"` _noHP;wu7ۗ^?UuNQRӴŠUJ)ԥZ*L Ŕ(/ +"#Uj_&?Sl  @6چ_4iO?RUW̵Y˥ݭ˺QY>r6}Z@I7Tōgnԧc.9/I8T͔!d4D81q4OSKyl֙VF \[mzQU}ٯ#֝d~t̖Ik2`w`1w&mMx ul6/mZ&`DݽZSDE]vJg;r4x4kT~1Vý,,@M D@"g)2TVKط`ujM~?WnVsQ(,BhsǧVUpTB-Pp(6!1 ~|w昢oE9SR $Eϵ2C&o녫Mȋ3mPeR)]bS'},N/7vʯ}S;=ʷEDŋxd \שE*;yWw>{[/ LGjQ];=1,÷;sdDã֕6B 1ĭ[v7e8* ~|ڰ$)9"],Ζ&$]ux!ڥ&@-ER1Z?>lƭ $bS̭͕v.3?k)MAcB Uj%|ʽ$bUj bpX)J;SZFJFA:AVmZe2JIBb"pt:&ACXV݇O<^ kTY/լ :@PY8f8|MpcTQ5>з|ղ$t^8Rℜ<̒6բZ)uFYl1]h (q5uEWl9=k7.ι__7w]@ԏ_XoR @D 7~9YF`X\]v+ձUSݻ:YN޽W:hqpZWKN>Ȟ{YV к\;1Bs쵺iY=5[̎cH\([B,ChZ}dfM-n\>_͸2qbљ,%{c'[TFzR { Sra?2ɜZΒo"ey&*;;lO&-T%$ULLx)@BEJ"ol]Р!I7~ĉQ)\CYU D]`~էMw~|yu egO= 9S 3"+~9[kZ陶=uSUɁt NsJ7D-{rA)wtE.dMȞLoC7!~{#j:>]P_jR&ayKs0Ou3ôtݴLf XQp3ȭ-)Η'!&vWgLs\[E.ɪi{\-ύArфѕ:HH$_вk|:i"=|M}3M)ʹMثuY~XaB A*0FaZ jrfHBE 8Ƶ\ }8?9 ^MG}r ݳ %QQ-yR^_/1Q"åkM~Y!VlAJ,7F~9H@,JOb >n77~iolg?.jew.q8;9ˇ[O:TGEP^.UFFt B^7ζ" ʽo|?Qfݙqʷq6]"Iw<ӻUz2F;k/h+ yPϳ贻CB7OE3_xfQB({/ ]3- 6" 7θ* S (Ccu5N+ۻvL:˺޾Tˬi2oίe/;ńJ$I7-: P̋NgawcH ѻbZ}Do}p+tM]eJVv]Mb 8sVAR8raNQB//\C% M5s:i@,DQT"MB92;o|[+o2oOFYսvr՞7wW$Pacq|ər"\$?jEjck@H(*l*Bڐ+W}׾ٙ@a&=M~h2|ŐO}w}*˻)冀 )kM`] @!o|_fD)jllB)@7߳ ˃e\M&JTgc|[/Ce +$[v{CӶS?׮Bc~HQ2kQۉn:}کo<,Xv0(boVs;mmvWo},#I"PP+kb Gh8b%gbsx:HFȢV@uem|7,H (H<~t/4s\kN9!fMImUlyxo~ B1(Tlk,:aD={k+K/NeJYcΑ"kP" :IPd|ǿ_뚍x* {v~>eZ& ]xf;3˯GlH@` JĠZ=ڴYd'GV)b,W#%*ك{%?_7m/&\\r[]~|wHBWW R+^ylvϿ+uy RHRR sikՔQХݾ"Fa ˆ@wٿ|wGDX2E6 $AyfNTU\X<2@4@b}J@.,⪶,$]04 ^ATІ_ܫ>pZk3͙];D)2){l2l;7/}swo;}3RmE!IJ CЦm߲FCål\j%se&Bfm(LoPbBcEEiHѥg?"D)qRѿYo1Ƀ/<{m{Vk:٠Y z)$k|Ϧa+BI|xQQ|FkY7UY\dALG :z0ӹʌ);W K~jXh^5(z1S61%"==:ό 3:mEy|ER6z||MY++cN\BI+hEHXP6HIC:PˑeY-@tXgPv,k`@EH` AS5:1]8.i `S8@!K$ X8 3(E!2m,f;iN'/~%eU)?=M}~6>{|8==[Ϫ@~OiՃ?ҳzc[־DwOo~//}oۛ/RFղ]NKXM`w$ъ :@̝_ͲNcVtڶB(HDrs{'F=:tBɹh`3Loz{;7n4޽PݥH9 jDEq]Vw/6KQGGw smmVi_J9 {vs~/' -!;8ؚ-+onnPy T]S`rȋr:cN*|\5L:N"V!1%Sb !alXj' z cl($1JqJu QR+iE";ys%`b`Ũ2(Ó έR0K/^xa+/XD7/9xk / F &P! (`DIDATm5d*U1rF}7_Ƃ''{g6y4 h޽oz_i]YbD. J68./o_}r_lAujuS19d%hmD"jeH>$f+J^)@}F$uHuh 2xRtGo~tJdGYbbh!O擳O~gɻ@Z֯R?i TY +EO'>y"s1Wݿ'ےU2|=Yz v\m;GBZYn䴚>5:ˣ3_ϧnbX Y"lov2_mHMF,g =mb[WDa޻1? 2B*Ϻ^EI"šQT`lbh$lv@=\)pߺX|3vLeD$T /e}pl]0:SXLȠ5uV&Exn1jL{s'oUn6]X|mK4* br0;gN磾K`"V=x:o;ݝM="իfx~Ha{ie;}nn+QڜQ[ B `ҶTÇ7._>+z&dzOW?s8 §^q R6WkQwEHُg7\.d`Bk;Ì2Evgs/k;v mlfIɬ敶Y Т^p1m׫]xsA%bmIeBLarq|~RY/^Rro_77w} 1>>`)Inb4mQd-tΏ0,2 Rl%* V AA#Y_&""(Ξ+7(tk~Иnj@ l:[tÕkUɳA,ᣲ^|c$n| aՀՠ$&1, "gٷ{|{g꧿7. vAE0ڴ^)&ѣo|/!]t:t 'pe/cG&JkÍj2y7_}Ց?{{%"YFHR mj$AD':/ 3TJRjRY\-o=F}PYX.2rEqĒΛ(Oq=u3Vάݭn>:XTSl ߍdlJB !c;O]Nއ DNfbpvtI o7߁,݇oRji-6:Ŭ -7.r|bLXE/-xH"DI!2@"IE pfńdɲZ')` /BX|QcB BIe Fc+?뗮]ȿ ˹0nE4(\(.Qi!DR$s=ʫ/ݼbњQ1J I(_j%nYW? ><:9y jI5߼v.tgL7EɽՂ|jS_! nU#R*I ZPtd֢9E 'YOAG"IUUcL% M>.th: Q_OlVخ}3̨A!u?Tw5[+֤6wmlo1^j8m^9zDezٌׯVaΪcs/9ƓYUYQڬkṡe9hUcĂh`@(IԶy^z5m_=:z0;v̴(R!|1nL:9:YaQ4[jUYb"80XRHRL0 ~` .$D*[Ә 4d)b1f )7]=y q\LIa pdԴ!,a@ S@ZAp !~~|t aaP3FPv0Q[[, + JtSNV lS"@Ԙ21aZ*-<||g2PW`3pMyeKFC$ɖÓg^L 'oh_lVt7|R%_vm`6vFhGnwuz'bXH]s"Pbe&&FU 7Fq\fK~jĄIbu 1LBL"MvKDS$@J!PHR Ngsco.ʲRJ{R I$D"(5(^vkQ/WJ DҦU>ݏ <(hki}L=$ 1἖R& >wKD+,*"2 >jĈα;`NPt#N?Ovr|ᛷW':['HmTk{ikڵkܸr-@ 8 @R*$΁k(JH @@͕PXN[֫zQZS[/ox61!:F ZWj#FWJ$uE)̲) ?yhن_B$VD^ka`y? EL0?7ɩv|Zhw*@-$[e|zly<<7'^0uzbA=#'':)B!MmLfNô6.ImI7%Eռ^fd2ƃAҶꐄ%##"VmC0>*Y!H"\c`AQ,U0dhcDp'hRj`H ]Rj/MTΧ*NuX)B %:%$,Z!)oCT&um{Wt{+_' `7ox󙗞۸q ]TM@1`(Dt()3Dj1 ir+Fh) GY;_s^8$dtY;=n!R5?ѽ'LJ-|t`Ɉ@Js "H /3k׾Zk, 6e;Xy?ƀR]MCdcQ Hroу2۶b<{(`< )PnUWϧ~g~vbK"~N SHClwaGr p{|xŹoɽ]]L5!<<8=9,ĘR J4'oDT`fDdMFݛ7ZtYiTiAi@iY"hiZk(x/(@NpH%T8FE%D̬JB6@H=ȁ4h ֐w*f$%( F.3 o!!.}^2gՠW_֬{o{W> 7 D4B$]t_@"$aeMAAB kDeA\v`(i;iY/vQ8bjTgcO_oYHA&(:/QiG-\wt8D-4[S=(\,&Z4(b}8%  lQnS/dno^͗dzb1)4ڗ_(lLvoⵝk/v59:ߧ.x-zG u3L'.|x#Ď 퓩 W"/7Ky=PcjY'>aa%4BZmHvg6HA2 tx%Y qj1iUaLdY"I+6Rfc0Ub *%*Q؋' U .zȚMaD'0 I(cP-C6! _-:hLU\gNvfnf1TBڗFQ"V]/;Ǘ?b9]co}ɲ|< D36Dmu̐:Y/dA!bd@d otxlbl1(4FGg(v2=Ӆ4*H.")YY*/j2>ybJ 06S A Y\ uK\+%AR $HQ<+?}C!/`QX\~K~S^$ V\W"* SMMYP hn0:ha9 mE%*2G&F!CKY}ť^-5gj덑n PrDQr4*6^]=:|RټRώ 4pɊ!B2 P@"aλ WmlUt+ |Dkf\M Oӕ2FI5mr@j4Xm'>";dzw~ꅝmjgw{s÷糱v7&O=.&wϏOz{xΝfi/ǣgǫբk%Ct)F糕n'2ԫ&HHgY;l޽,F[ AN-+%0Y/MӠW?&1zbP ͢iCJF imҙ- 4 ArĀZ P)U-mE`A4BRJPV~'rx轷̶*?Ͽb35oD@Z$X+R$blm9p{|5`i*X(PXM'Y[z+YkڪKBllt61Ώ|>~hy6ݽNkk/vU͟U;99g7]^1nI٣{wy'??زx_՘D8jN~|` 4"P]r|em4Ju ʹY'"+F˗SJHZP0u:@ݿzpz~|YR r{4Awe{&j/SDWܵoA&U\4e{vrT׾Wptty_;\VI-ez]iǡiE DF#@yܦMXE`@p`5243B-e M!]S%|AjQkׯa|fa9r7/yjŢ66;_ʧ<#8]+XUR"H)% 8o0g#DeBNB_aǿwGk 2Q.E \? F_xoPv4 ֝fvpY6{ʤi!b}<+]FPV@E@1`f;7^(nvR`l<{V''g|`5z?_#G}ӯj2"&GCղ7#BJQ,˃7~soT=kK/ <l(k9)A"Jk߿rA?O|[Ycl88=9B"ڶEU"1ʐY4B6&BJg٨ן{v|g?K=~ݢ߭tܿzV*+ k0\`q߯&z9?@Z<:vgR$ &9F 3TF̚zrbPcS~ZI7kVmDPI~l.*ZR#UB9(2 ^;Ǐыi͌O 1).$Aԥ#J(fB?yӂQm8+= ΖGğ,Gh !F$"DH5LGbij(8gM? YݛYkQV&tJ+џ=&I 5ZnZ$!n5mF~p#ȵj!2-(0$Dy~o[}UJIRh4`T ͚kBk8-ϭ~s{e:oeo3I(1#ۅ͆JBvbjNݢkbUS7չ<;Y ֥b)yͼA!ueUHXDj_:dU-6zӺbBU6Ĥ42JdGRپt ʭ*bQܳ3\on/\uo?)+;ݖ]wi6>S"ʖҶ kVUmȿyG__>y.''T_uxڶJ%;͝,uyiwd<X*Pf],OO f I:VEo,Mg^ҭ I ycF[1"+clմ6]-ܥ$QaPS(Ev L|m`* $h4l]0{&°f+v'?yJV C"a=|_Qwyx`w~z5bUJ0  !$ X=ʁ[DJ7ٛvm]o/J>{)fCI1&@7eۚ;$6:v6sI) jI*3H*Fo͛2ʯγD PmQ[5!C( zrQ0`'snЂ2x:\tuG4msx:oϜttugMddLR)"菏;@aZ|fY ҺD\.d!ՠe 7vVţWm37:}-ipi!$Ibaж&#@!)Py: O>:)iu8993T[F Uщʳ2M"%Q'Nu6A;RdDBT%imӁ/Q뚝wbk;=͕kstNk[P(l u]aq"@|̀.fP'SҪݤewy/./>OS Jy*[w>? j6eB'EPY+E[H.`d"jr9WR {@q_?ӿU?*{E0m;!z ΀AVRκWόF*H.sޖnRwpѾxy\6Ϊb6/;S2 !s!$}LCbMyk G÷ϖח"9,;>pڋWsUJUT>z l1ԇ˃沞Z/n; 8ǓnӈrXX}2uFx<ʯm]/NN/2FH$\'a(5l֩IqַŢ= ,IP)c%+I+|Z fݛ_^L!C)c]ʹ0Bu1 Oz#|NW& aL) A_X iIRY4Zևm~[2{{#vN,?nsw|w{cbvGRIF mP2ܢ ʂv;F;"W?wO?.ڪ{{~0<<($#e!$')9L(ƕ?nv,H[,٪ڱ}]V-@B,RGǷ\Q $DۭjS>M,"U'vg OzG/Ms@K7]bbn5%]N6ŝi㗧)F-bJi",\- kcZҰ̗WŒcWՊx:%2Ӄ X}`fsv\$x@Ub0QU@Фu4wcBvfn|;3Ph h )[mI>65A鬪eѨAfH,,jTXh>QC`$H"QDrd ͸kW??}o~~wv?ݻAAk6VAm@‚J XV$ _w 5Hn@UXVn9~o'ʮi.O_^YآV7. /?"Am3($)(v9wtwgS@hV )"T@ŷL׋N)0W.g'g^}_E玏omڙr!ز#ON^Ezv5;;a6hkECFa5Y3t;ˋ~jԌ_7m?y* dtg=qZ`^6 !M̎UHY2 շ>xqt%Fݽ© BAal( >ͻ~Q9vHP,XXUEC)mHs`զ@+g&ieY &Pέ͏?Gݦ]d+#F9 !ga/dz;?G}_KGoJ=z1͈Jnp @YjĠ gMT"+PiAD`-(JDM?>Fo{5M\gۢ zst|}p߹ɩ1\Ae HЦO?ɣG 򸮴5SF7+m zT+e|Bkpx3m "jG/b29}@j&bGF-*tgڴbs}}fsIF`֒t{oIYgnyřAiMΕM9!i4M]EI|GKhn֋9,.mۮa΂wAi+]OvJ6}hh)(E|@6UD &>y1Rs!ἎSܟN -^۳e{uMUPԣr"|1 * 3Y R"{Tg9qaQ &L,> xӆ(A`x6aB(ZS RzHzA"@F(9JR!DAB@BmiiܹWhUJν5Wg?w@J+m&@ZW_\-嫳'7j"DѧQ$Lu`m-Yk}o?FJ!@TIi[jޜ|7={js+BQ9R+u]>@H9cZ)ő&Ѡz÷\p.u27USol:DN/ϡ Yu5?mgT, 9wsyJ-ۺܽKԔu[RW,점be]ýC $@$)6Z\.;bΜآ(Q\fj}ބ}H! e\-'AG(X,iT%W|CTM60e YnR9a{mOq`[@;JI@on m em <+Jq#IF s@`#rFQ=Aܞ_/gd ((͑"tu1F]=Zwm=2>/|Yxtx0N+2E9Z^ZiTf/dN?{#M}jm{Ii"3]v^]4J3iuHW ĐJ{e'M,s&bh9gD*H0)1M)Ѵ,˶,oPj;|w( X׾ FTy[s&`25ay}l-06u @#jH$T9ܵi얛{.J B8UQzfd~~_-$o٘9i$t]0NPvBeo|4soݾ7;o} Yo?rӟݹ{[!UFDnզ#M̙-mVOS#*,g\*O#bHsu2ͦHɐ5}BQRb&iTtp0؅U %nW?;ki2^=/jT:N{^/_=>UZq_'rO?_>06@[x_.8z_]q?uEŒrbvX #4sH|/t2n9*7_ ԲT81FrVQ&ӥz1aVT&$]V2l/*mBENNti7hn]soNŲXŪĜ@w 9 $& n.}yC0[!B-(E:/ΖWzg/ꅫ2$J;]直;W8$.&TCLj2ۻ} yLC0Mb~5N 2*qg|~pWH?<9G?_wO`0Umh\&NAbɘãa23$dfȪ q褡ԽߗCC᳝gbp4+xq*<'2oY {ګ'fTѤhkz9O=\'ݛMW_JdR,oݻsrztjèX*$M/$^7LdvPHE2pΤ7zZ ƚM8l1_v* DEmsN=0M"6,tY/C_z420MqLecjfT:vonGP6vr8|gHJqk5GMmk"Ld Axb}959 z/x4shpt JqY U{E`E7PQަ$$D"R_w.m׳=F웸4/O|,m5{|x];*aKٹ/_ظBrvpΛU6ރL7?j61'rLJe[="PNs mL R9oKpDO2<~~|Uv=_}4AzmQ+ uñIu58><#]>=QEU[뫗u1\͢EGӋtgp\jhV~0YZ61. Jip0P$mi:X.lduvzp|gb13CKZq޻F's84~Spqz Y#B QB0l۠QMFâ0WOɧX:Yf|q BŠoÇc41ae `R=8??Y6} _{GY * *VF@ P% ofmArEN)2nܗE%d I0], L)$kA8դ )*,)N ;~2خ׋sNtq٧_=}㷿[r@i=>/OzL=OFw8yQ3T?gO9&o$E`̤u9;H(M1bgrt}hӋ+3m??{$Yj}@ S y+Wp}_7zy5Hjqqf\Y@cr@Fe`t1K?pVIl۫݋VVO* )Mߦ^*f+z:B -s" 㑨SxQR&,̭mk.SY)T`w'"9eB`2 ַ&!lș,"oF v0G8~mN[cPf_'V/s3qç;x*j{fNAdn T[3h-I!э7=?s/cj/ 0׃Hw_=ɏo~1۹I8c  69g@Vp()Z3M@PUcu[4mWYEzzޯ.ޚMefV¦KYi?8Tfq7p!aœJ|~]`{kST nlί`)LdRF3^N͂$Nz7jzϞ.71;/Q۷n//7k߮03r/gqWAE,İ XډXKNgS$L̍H5!e9sFS{kVtD6T2N5v72C3׃V,gm8F7H7YgW_|vxpp(n ك/qdvd0 UYXwybëv+~ޝ=ܽ55hނXH# (oN&A# F#)EhQUgFHɯ' *Ęs~?wY_.sN;xzV(^ljw^ E1{[.jЖ DVa= ݙmw|lڣ۷zWkD>vOHJk@7lo.t=@DdJpnZ2Z+3 FCTv\_\ԓ= 8M"ay%WBV2HFQ"E)-'? }ŗf09x͝{|8N6؉jPl zK7l/OFEPMXö\].^-^xgf<h*DXn#R ވbg''$wOVmE 9M)kuI )R G#R(̤, 9"sL|xj WEI g P; yTԕ[EΎfn^-z 'Gj*֯{FxR\͗ڣT*Nx30_}vr~ZvmB;bU=W/Ɠ!i H<|NY}mOS!)F1[{GbU\sʮdP/'q]N՘/^1XocDMmUVͪ*L=,@ !c "Lg2J)1)EBkqŏʺ-:*bHRV*&!A6yu扆 X@hȞ6כO'|aq_;m5@5b; @xۧYxvLdU%B`~f~uO䇛g_ vVC%N ŢJh#)Bʒ" Q^νwR FU~5"&κ'U 8%`MKԨ4 IefH1)$Ձt44RyN=ǔ{rqxlx: B9ҁcL z+5 [G7 \QX$zr|>6( S]xq\xirRʀ4='׿) 8M+1ZrzTd% $n"x%1R6Ar2Bh__}dX%!:v: fcx8C !Lg=n! $tZDExHH+Ƀ#s ^khz!ǃ Owj58XB)M:@Iu8hF:ʟ>x֗Wn:TdY=@30ܠp@[%nm %Iߤ0$An4i.N'g3>o&@sj P@2Ҩ~$- -gmP*ڷ~gX x1ula^AV9f{Ip"1`]"7kGQ›6%DX|~S6vXC^6[25'/ףN?FRZ%ݣf3F6EqiјitHj2$+MqnA!H U޻|qW?{d Īl 4` }u"ݽ+&ftDcwgm8AdH,ZWvJ'LUvtqUZA@pJjM}4amkir:M+d޽{! F/qUHQNz0,C6Y@Yξۿ>~X{LJ4=BP@ X`S@Ƞ~2Qo"r'x}#l7vYn}4Di=:cvqx]{d~ҕۛ/:ҷ!'dڿ9 (ʪGt5$}1buؠ ̠D bxdN Gigb.2jܛXXJ `!,}ߥ@8 (La5_~u|X;rÌrx+ oGr?)֮  Go(|㰸l‚ttp6Z\=}ɋrB[IrҀ$5RV`JKή@4 4:4!X1}(l9!8Rb4!pFs&R YћMIN˂&NO3ףdv>9Y.^]YN1e%}T ߿6: Z]gQҎυx<-M8UTU)$vJ`̞9J[m1,CwL'T`R_]__}g_gO媹:4 q ddݮz ̑ZrF#చZY]T\Y[4 H"Fua>1?z˗WɪUf RTE`L׵9fsz{dZ[kH ;*Ń_>^\ws;|LM^g.~o[v .(y BܾvQݰo,7Vz_UQr@_RivAֲ$ 3#"2kw&[}B޸"n?7w,t7n A}7~k2R[0ATZ~0?_n  Kbonʮi~룗ϿK*+gBBLFrj]Ylj@caL"bX0=g? #笺atIXT0{(@C5f 3&t>̯/],_=uˋ}l]bʐ !l @0'TJb\i(Z (B%rRZep GIϮqUHw-b+Pc%Z#)m+ gf>VU '`xlQߵ"qrtjF*3zVV!Zˮ]^}Ve]*sg?~|t{u >$`pVb-^])@ @"ڢS lg[ fbś{cD@B~Z{4<θ `*n//" EH `( @QH2p!PmDž2VɁ|6aJ[ '  4e9w݃ɽo}}sng) 3 cm 2+X$'隔:$C6,ƪ#\aT┲pF ? QJ)Zoz6Zu ^W<T.ŴZW\,_]=ZXNPϞ*t1BizPj3b|׏.단uY\}ޭfwTR{цM&Uc8?esqEZ@Op2$kG fٸl:mۀƷn߬z& Օ<)L?O^>ȬvKS8e0acUH+ŞT^=zy!mY# k;(Ui10$]X"۲ d%w>8T֓k-O{;Pe0"BA% m\<\ozq뫫31 (]51Y[AdrsdJ.D=* @gm̭S9fQ(l""Qq"")E̙fyuNtcLc]I(RH3*,Mo[ʹ޶ְ 8 5v'^5m {X~A9MiݤVc|oql6DW@lS;pf.^4^EoɊݣW/>z#<]\#!M%LJ+EjrJ9Y6pI>moKDPF)EJRJ7"Uu_}>(jL!:FL=,r#P9n[n,nQHшw[DȖ9Vg!,"9I P @! sU$I8Θ )ř*TE4C@AS dV_J21TUZӰYLڮ6d'tdlڰXB#7)faURh Jm5.ßs1 befT@9IIfD.5Ҧ2A bJiQ-;R=l|{0qd7}߭8q:LĢ[Bw][8O.泙D8dLZW"!) Fc RH1쪢P + "GVF+"(J :3BB0 sQ떏r2)ehJrBd`0IT[$[@sx=y~d=N@ؚ̘$,C«̒s(_Uj\.ӟya5ݺ3;)# =` 1b'M_T5*RKdYrJIDbq/z伋hB&MV]\ 5䌀u/h} 0:猈:CVPmt1!"rT"9(ζUOBݝZŀ$\4Ͱ*҅6v>_mή\__{0FyD@ U+-4)H@2:CEe h֥SSG.Z3" pd ژ{Ι>g_Q (d"AG{wjS$m(Nm6j h@gNem@ذJhAPYSQ+R!BTYWb(yP8Vg/ON_v?[% hD0#!1Y ۮ^t}ųGVwwћ9cք @ll`@PS[% S",&N> >Ͼ\-O_>YxgdwowpV}tш@u!nť뫗uI iAL91m@ ,\U6h1ZId(,JE(+ *8(4Ds1d#q`(issqz' ]|w佻;JJ}{mzsgP7)[gJC. &C;ҦaĨf?Bljc %X9%Zڬx4VleY101p7>dĈ ͜B 9JfaDBE!R,Q|l5#!L#4,ꐣSLd jcͺ(rsc"+Q@tEYmg?x[hwJ GH0H DݤQ$wnI9a_jzGy睮yHsq%$V̠- @E@plYLlzWz~Ǐ;&{e#D֭!/>{#3s,)yzjf>("X =s'%DJf ?܁3o,"$eΌ$ PbEhgaŪ#bL9Vh0> *{ /8 *6CK{p?~thEQӲtZ;C yMJ!Yϱ P8žaRn. 43&DaU;PYRΎ5䜯y/֮(Y ^ϛJL내Qq@2Ҧc_)32bU]uIb,V; Cbl#QrشYRaQR8"M"9@Ŝ_YCT*.m[1%ΌD;x:Q\_}zpoãe(c2B5ҕ_>b1W̬m)T.JRI Hm- DDTVEUr}h//~W{xgrV;;O~3ʃ%t sn~yq資_ kD4nPbUW!z̜ɰVÙAqn(@A@̤P黳N f-MOWmJYJK(6MA Y D@=+!ǜj@)M=-4O>|xF@)8_70!H @tUI=iTe 9R\vMcSBFHAQR5ة2 ϻi8(R+ 4Hkic2 }P9z e&IFaXqt5 )u}ǠUb dNHE.T v.6kb w>&TXD(K1ykݚҢ$ #u]@Vy1J!!@@7GLqwnCNa|4XxZ;jg_rSiyvWogg1jUXUxS팇Ghv@%9mtRQڸu_go|j<0!SU'g8*Q@|_~tֆhFU="o%V#J^9 )A(-%" 1A5L.zA.$&X\SvхU8u}*0m櫮iί/ %2Z.cZ NwsY$Y#[vnccQE`fN!ժGY$rиUkgU![R(;V#RMiicR%jn} k9v<+?v@_ u""eȩ>apqUD2H)s\.`R"PH9jzm-l8ǵzϺ~}擧>yӭ0_55ͺ_p5߸{Yz_nȨ`kn n妣1)$ pL I8hWE;ݻ{٧?^]`LjM@AXtHgktL "@Y (op,fj/Vw\hJ5Jc1ˢ\wQ=)-t2 F‰9#a=0)E K}EMQX1DqUZ H(眀b>n!ή)[[$ChϚϮzqzG,Rh.νo?<²N.čOI~}v9YW[o5 &W*6:s|W?9}|ճgѶC>y]~=ڭi9*z#̥4 h$嶕X6k@RJuh8۽EG)-J@H5 r ެPdimc5vZQDԉ պ\(İ جD¿l5Xkb,V2,M]0zR)](ȵ]#SbB&$MF벪T]9LJY$(ȸ% N ֋CkDR?R(>""S֔aAIYFW?ohe+q#`EU싓_MۉYl4:z>_w[n0#PD#!bhU]hxe_=W|w7{wWXtEZ[ Ȝvޖh7y5?4'#ד}G'R2珟/||\S{ u;}JDDB&m5x`H}[, D$Q#2#$eANn]\.sߴҌ*7)mN1&Jk9'\w&Y7ֺ } dBtTP",)3 3K R4Mn֮t",S6щ׳4]Zi#vG} i xմņ(qݲ1J2"t}}F+MNL8H-rRjRc̡9$1fN)gs嚮E3ƀ* QEOibľwJlYEFM vJbc3YXBĐ &vfd4(E%R7'_]|ՃW^CS7 U9A7!+9,9 (#AN).Ewf~ѭVrq`<+'ξNFt&[2Hf,@r HxΛad:_<|<9 )׫奻.ڿ_=J@+L,ғHiA8,&@&QŒ@"a$9?) 0% R01B 9kr9o旯TzP81iч؇ԆwWj_m̈R5AatB7%w"5m\ ބ%cdmN=0:@e5& KcK3*䜈%jbȉ*}m%&FKQVBb- 0 kK1iPA2BWEZiB(ZFcLJCTLT J+sI,PefCb1zݞ-׷own xWZ%_珯OWgF;Α-ƨnzi8zIJJz _6(9 ,^={x]^L7~[1NDE(gbf6O?k'|O>Qw/=уQkP5J+Gԁ&cvhmsw?1TںʬޖJ7mZf)MY+͡"9Ǡ.*_(R"j^,zAb$KUhP%>9g5NsSȚMisYdJ1)2dR00YAF%F(5M10GZ3/΂3fZL$3eFCǬJn4((…)cjsthR}H1VzkJJks0,s6eeԯﺐĊ,$ZI|b$ưVz<)CFSLBa*2x˔bJA.I+j<*NN~?M{G^'9~vvj݃{N}6*@:R,H@Rf 00Im+r0OٳEUk:MUOn1;HS̤(g`ɜ2-Va9*-.Wl@ g.}/t8(kJD`άhEuaRr2"*6 i{wX3r$uB "icE`wtMͦ۬>İK T]Đ ^Ĕݙp"^ !f`:oZؒizo>1X[r9`Ѥ:Ț7ẙKN%0DE9(VF}, [Zobl.))GƀdP(D8HY16& '15BuQ:v,DŽ^p*%NF{hI+s裠.Mh!c]NbtKQB8{ "Od:Eŗ_<{lO|tL_g[rɐ @ g~adR@9rwO_}u ^>V!v1 Q1G`dsc >kabNXO_8APho6]⾗2"+aA2 bHDoKY!udcAnJ)eM ,fAd ʑ01PSCuB)T@[3CH uޘ]KJrPU TkMiEns^'V|Ū,LXfw2) ٢FA-eMuH<sY` uRJY!\(cH@${Q+kaDmlE+1N3`cJ!cbP("+ۦ͠Q%ҡ)\I"&hJֺQAfeAv׈7V_\>=Y0rwv* .$ĸ]ama| fF@Ȝuc -A TFĔ@&zO3+ƘRH!&N>yfVmgG>ˇOJí`h㝮_v>v1)1"*@BT H0BĔ3!")zD9m@2azfј)!yڎO8F޷VY[T1I g$S*%'aNMBty~^Kօ`hr*.$_5-l fӢtt*k&!3hm%t1#1eT]$&l ȽZv|ޕXgxXTZml*k TR' ,>3ʺAeFH1K2m!۬}UQ+K e@l}̘c$ 23DɐDiC)DRn *KVy>~Ol6Z.+RNĤbPF%m9Y@Hi f)n!b IףzW 3x'U 9ІzCyЅWM_}B2CiYp( cesu\ `sXaUu) mݯX6,"B:L,6 FT WD:%tQŬ,#Ie$Ydo6:q+m(I8BGGMs[.yۆa M5A%[Mm}Zf骢tzU!.3ƒ61!B(Ô4 q+Wi(JUj(SV(Rֈ:r8{9]N;o\}@k,y7]-)4X=s N)ľM@DVMY[eH+B53` |.+/./ܺX7(|_,!Nk3 )ĨԖ-H,_'0# QAD8%,91'h2{:oO&{S޴B7}7$&!^ŃW 1r-eqHGDBsuUҋXo*IqH_]KGo69ggEUt{|_9^rZ]] KΕ*ez H6&gФH$1"l,C&)GA\-z߄$E,iEN@KeəR ΒpŴtʺͩom6!F\J!e!oz5 08 1%J+ti;w1r昳0,DwA OMJYF4+q2+튢(E2!c ShS"̜R1mrJM$i2)ן|j!kf(֬"CZIBR$Dl1#$漝r:,>1R1)j[~o.67= JQ!Mn|6~w|5_~d0@a0X]4me BK"t, uQ=RY?|wqmq8M=1M*˺;={QYGD*/J oQH;F3&1 D)BزCH8)8gt$LݽiȾfn- =1ccNQ56됓, o<[ 팸,JdZq}Jѧ`$VmV))g`(P WhJWBƪHpvs Y*pܱC JfFΗ rhX*M F RByXFID0A +M:$c9Xv zyy]af8T5n$đcP@ߴ\%0$IkCd7) T"g,H]o5]svG/,[}m~ݟ_] y o8%tER̨2{*n8jސ26XeI+c6:Y1bfDୡMWE:FYkԠr, 3@$BZG$}qY6!tRl Y@oz/#~l2`4 F.RSы52 !^DPP8MM$> h ,pJ!D1fZEM&s>.v|>[]\k5`N(@*͝^,¨]a"@iѠ+2^VDO9q1kGhb&{?Oudw{TcaFE,5TR9)۰uSNA8 Vq+9ǢAf̤41 t}4Mט95 ժOLD@)"Ye H$g-"P> {96CHrE v!*?T)G"͉) >$Q)>J>p.@D kErV#r]֖A5Yf`$ Ve]LN9)R.>{xzݽlw8`ZFd* WD#J+9#s c@ù)2cʙcKلC9CN>" ; C ͢z9Z`߾}XAl[Dɤ511 jz̻hR`P7[o,H7emV<""Bm&p"֭^XW#Hie׊JW2dbBj(#!+wsY96d~rF" tU3RZi'{,`ah;U,KXV!uiw>ͦqsŨs"21K5!I 3jȪ!et|Td"dڅIi}nByDALO\"(ILW/&"f+CXI8K`[ Dfni(7SMQ\ )D2@7)Td֐R [Kl.r؝㽝A50;CgIl5 W ѣ(HP$9gژE8l?i,ĠQ99^DčyW%iR2 z  LfIGנA  F'L2߻ux)v֚s\ǟ~| n/ݠ7jZ[.|?~z% 2q+Ӥ M39=ژ9u t4y)x|o_\ ba4}/SJL87#=RJDou[8Eu}x0vx8Glt$,mpt?gIjsFys>~$bILrf T Ihm53P )]S:Ȝͽ0s^+\v_/ Vk[lvV$TT{k}x?G <P9qD3C.Xu}YzK5̒!KnADŒ(LC[Y݂አd@ I0M6@IqJS Ol S*%Z[m޻Bq?2dMQIDATi<`\^t79w/0|z$y:fHe[6sgk/-*`߫wnM[v׸+M0DEj]ګNӔ 0 uKe"mE@dN\f?z,Ȉaeo&|A$ҰF3m@@Nu߭k73eFhzrM0( S9?qx,ٖStkbC|Y4"G%Uu"tں$))$avwr8vӺ,(VHo2<ߗyn۶*eY}YֵZz.tgY5H9yA]CD yFml1PpwNǶ.%'sRw5s5Wu"tk} 0 \ )DtG8'ކyZ;23cW%k=\I'+ LtUj ;7AG4\omp]w7}:|<9a]8BmQj\wokZc3px|~$rNL$k26CiD\ P! #KsL`ח'A;U}Oח//Ħ][_.ͭ:AA8iV#R(e/[xRpM yoƒ$-LXiaqЖ@}xx8/~|$x0޹LMضu1qŀL  1OSt1 3BvEt0U6S>L;th[ov]^2ϷE_CEG(z .ɑzWX#Q2<1MGHet :; ӔY@[%IR5nJH.ĒXPH:^+ ,[պcXՊ!!,2Q #!vU$ЮV"an=TdAuH,)F$=0qʓ졌atS赵Б=3`y;.ܛOi6i$?JRD$|KDo\9 p)Hmۺ.?}t>KajO {Ufj8t>fV̉ Ήq\L8T9! w}6 FsuI2&L%l]S\{G.s$$4|"ܴBC.TzOItl!JN…p.RWu[o54Z]{P[{k-/L{|(SΙIRڢ۶Z4MmSjGDf 箾*!rѐILA$y#\n"6 r7֦{<$<|jʅURc@7C"3_ H{_pS)0IS D4D)OnA,]|]{9Otxy]XSJiYI%A`0]G SK)G !# TL"r:>N}[k^~?[o/J:rԖmYUЛ1tӜ߿kS.Sdzz@ $ZVri0VsäKW$P2 Јg ›CDڽ<L%[$HeDYR _/߯O[vi,(ҫ].~-kff6Ln~t]n%r_[&Dj;9hl,Lj]oRNxeRfI%%ibQޡtx~y|3- ͂<Gszf%QW S*;1E5"~u6L궺h8$rrN9Q;cHLsIYd)r&Tj׆0 =4$uJi*"Б3VQF G$Uu"qs@n{*"RC$w@к~\ܚ;_oOִT5: "~~9vaß9FXLY gޮ90i,S\ F$dsX+ӜeRkzJMBL*| 0fcR.))B"f뽽{5_unmRBE0xK*u`a%$/7|[4y6,kaĄH;nE! 0!#BT&E(\Mv$TwwffGw FkN3'Ftӄ, =0:CvS!P@dK!"f%$6O10Gh5ljKqkrdo? `ßpkA"fB$@r a$ zD_LpWnZ./?y=?|x~;9Ynt>ߟ~n0ns̙IorTfbnmeq*I81LtNl]7,Rn,kW|<0ڈCb~,y*8FORdRbU&1usgH`03aN1խmp>ܽ˶+Qܮǥynqʤ=$>T6\aekKmNݺp%ÔjӠ޼+ޗ/d;rY\f^`‡Ǐmj뗧ϟo>=k?ӳ;e|y=ksFx<A% χ3JWuzօr& 0[ >>TማvmNS*8KҮrOI]E{$ڑyq[J_VjrݮW ׵ݿ) 1{bؕF0U46@)I&{x\ڜI"'U*={hjeE, %ONSPSsD@DĄ"(oBxA @fj"A:Hш@(Ђ {uCHU"G3PVB䀃P( @lf̳K9>gDy6:""9CC0$$i ]3c"twUc&>ax8Ƙ5DjC?==]__/?FkۖKxvW+cW49HH>TI͇ A[wm[/rw'L24˨E8 ,)) z<صEvZ,PrERNH)P(zlMm^rmۮͳHY&:E jUmkq]G sI \׮%buk zZ@El[ 1F’D$ˈ ea@8Ba9A^qN}eh(t!C@ EF6'IDh[+1)r"ANbD)@DBI$u- s1?O{C?$7A0'Q@!@N0 ' sBR2)A2$Nj=,D1?ϗv^_~orݟpmˋ/[T~m^5[N29%<|8Z9Oye]nZ۶nח% <r|<firc~;ovzSuPW^/ۚZ1b4cG$a >KkUnvdュD$1€Q"3u0lAr` F!A!6U^K7Y_K 031ڕ U,TnJ#njC-X ߿F$GDě: "o0(w܍)Dv$"bSeDZ[rGDs!Bpw&";wҐ-v{.?t\~ϯKmivmrRMD?~| 01cDǒǞH.7 B21RC'!Cwy:3iX D"\JJA ཱུa>.WF<>"1%Rp%l xAziԖ׮|mx[:KH@mD,uD@JCZ3!-[wb*̦6r #`LI2$H" pz]EĦĄ"T!(0!i`$)3@HxNuYS|ZC9b KNo/£]LN78!x &$vwhL@N{owK)0BH w'Q<<MEp$SbnP[k\ri_@YވP)X_BI%n #$s߮nQ QL4'a3.&n2Us_7[~u-\y,Bek'2w2%#d|Y4CApa Q[X܉'wu!n~D$+rdbHdQk!&SL-L5ZBhxo PmKP$ʁ&U[| z`[s?8MȄC։ss1"X <"#Y GZ[Eԙ=w & { p#܎4o~m߾~~m߭si6'?>om/(2tw Iʲ޻*,۶砆n/V2徭i>B>7B:+w`NJ1CtIjme; TJBk@?ծ}PF~ֵ>]\R"*"sЗMVkc.vJ yԖS뺦U۲lpXkHEjw\ ()#8B&ro=O%!l!!z9q&@ȩx$ Kf–i ypk0Ws0U|ߦ5}* "CptN6r`hD؛[ 8~9iEBBDwC 6C⽰C,éF[D ;"DI$R.L@0|˿ /^|~龼|zYvӵꜧeӌ??t(a,=̠mibntr"NLjz(Zv9l)gI]׭[2H,i]#ƀ庛*qH)/5p# ܩ̿h/o۲1B{Wyl"s*>ǣko4͹Cm>OuiLéG*,%XbTdzi0dY}Κ%6'H`fJq:YaXZ4=01Y$&*]3#Aubq 2^9!!b0Q!z]{g "a8,"D  a>H$fw'B&+%p"F3FĈhj,A((r2o?Z_U,nϟ>^[ Wdx)b?nYHDC]+)VLEMPI&3#zz˻31n}mRz@iyGֈKT{Kn[wCb"D'BV,b0|sz#,,` 1=\ i v-2!8`s SG]@䣤 Q7 ^" b@Q1J!#{03Ҁ "EOIÇߌkvy?wi{IbT-!gi)nRҁ1)rLػ3ҶVIQIJȁ mdÄh]VWYL$ TBڡ^Hr8yVDFYj5]rvp;NDlۭZOdڪ*-[ln;X'L f-Njz$>Q7,0#eG8eU#d֡bdj!ŔO r(GdBpZfd颰e~qx9y(JvLG|<"05*pbp3"QSB4W3,m'p !36+wgfwG[#|2_w"[ QUVNVuO?OO?R oLLp,pN*|S&rR<ƜQ8дPR /%"¨xTwp>4ޖ5֭r(,Y}C{RI|OR))o]TʹψSZՊR [z[lQ=:[Xp|p4}uʅ)}޾6R r]m583WD0 3n  1Op(%C4cLȄ-mρnX&D")_o>OHd AIH4ь#0]]7y#h,ױnf}oZ5"oa>gb< ûl}n 9ëY8=NlyNF$%b"c>:hsV!rJD4 h@D)n$yko9p<ǔf0:M‚yzNHmݬo?6Tjjoj^;^n}s$43v0[~2E1_,wDa 04á#%He"0K .j ϝs L,_D C"hJ;g/#][cL8@S%~Ï3BLnND&[6`(fA!#h ie8B!4xD!qq1cmۿ]~o?\%]qʉ|Ppd$i>8xJY"4ܻ(T͍꺺2f RBuliA<J<2 PVE.E<-D]#8͒3y"֚y}}H)T||mD)Yr:Y3 |jąܱm^Y~zkScV|G2Q05ZdN{߷=&՛҆ء3@-1T9#\e.rN8؁THMS03Sq< @b@:qA|u|Ю7Q{kAC"` ZϨ)w1cdnNȀaNݑP!5q"q@0D#puX|w?+eY/Ͽ>w' |%COx|&S)$ryQ+zzncB$I:8a.s>9{D ?џ9T̤̈jok:="$ b!I=:1NQq0% ""I " ͕0; "xu@#9wDD ! .`d@(Jcnxr Z]kקw_~O^.wB)3cJGd4L`ZEH!B=8tۮ:D…ҽ ڂ[<8i { ˌ(^r;VZ) | p8Lt¼U{ -pw"$ 9MRr)M#gծm7#k)("@? ᜧ2P"%9:"! $vuc`ЀG%|X!29 :P  q3 hMHFL~"Q N^2{g$ 7cADSIX?[u~>Х>!-s" d9183׺$#2.S"n9T# І$-[OE5(A9 uJ3¶ݗtM#n.D[n|jfxpfB@2!'8-Ir9iF MFn_G"cTDI‡G;'b}Kϸf"\# y$"eӠG!cS19{lEp;Bŕ!Q=\m@cG")3q&d`w7$& %o^km[ey]_>S`*mӉ !"'"btEdS(=IAB<̀@~7@o18$46  2 $ /wHCא$-1|d@PR@Ѱ-2e Bd}[-|><kUǵma~;Gz_6u:ƣЬptû8Ȃzn ~|#bkz} ɄfjifNpA xY"ӘuDIu`PÈTXqs21"D Aހe?( $TmD$a^@z*Hɭ!bb3s  ,HBDa`Y;aܥ)i>2Oү̶ٶnz{}yr\ݝ^rs$$Cڶ;!mK'6"p{JɇN$eWEJ}Bi7F` f91)uSk̜In}?Ns>O,8"CDZ8'EwpOt(9nϷηSQZm]o1Zo@u='ֶ4SJKD|x|hV[Uv\ `1WcL 8t#TLhSN[DΒ j 2s.ÃFeJNCd)5GIm֫(iHߒz ؃GpʜPBPYDɉo+Qq2HI<D<߅=Z;[ Xڶ֗A.uuᄄJM"0| D cJfÝ]= ab8f'; "ޓ?`(H(Nk5#b~;ƠT9!!cjwxP5G~0A0WiѸ^#@`D٨18v8="Vۺ/z?ߟ^ݟԉ>UüojEB3('ݘSٽ*ȩV$jU$#G0cIĨM1$CEBfl@h6:NP ,a%"Qx90 0&S9rHE:fQt8jtS\ $oG`Rp:Ny7FČ6(aCӁ `J#@ח[璦<=+wȕh#(vg17`@0#3إ0{E cIA É]˱R`xhF {O7 DAqB`FPo2 HV"\#pAPua|VIĤZ1ARiY("&WDZM@iA {w9!q*fqND5t *OL2Nn2DL Hޮ} 5$@!71"[C{ {g`t e`E<* FÝH\Q0ƭ2DغtĬ fQU`&e*A?Ē"vPVF0CS)MW^^?/뗟l!LJxݪiߘ4vynI`ZN9YX(s. @5M%CPjߒLYj8Oy:*ap\J [8Y2$"HֻMRcVAC$eʂq.NM ޜ@w<{"f&;aNC{0Qvh1Bn!)G1󠎎ln3D!1w8v/^I# ywR168 C(T*Bbnћ5h}ؑV5}48vylqV7iÉ͌cXHAlX|%"(b Gp 9j!n\>~er۳]۵}I3km@-ͶyVג;0| 6 KuZg&u%.|PN%uxnT\+いP0 tzڂe^j9WY-jCpL(`@ t<g𠟍oԓ:dCb>cFod&YxzO @Yǘ .n{wP#h/]!٬#Yk"،SRaLwр,ɼwD_-w(7hqG\PTaOG`&ppf ; j;0B}#JfNȔE7&1n}PKжNeI|h]{]^r{n_~_oէ뭫gy>l!J@w L杈TB0an?0 Gj zhkUaJya-4m7mq"wh? q o]cY))vhҽ] V|]O0 †Vk=;@K. W$52PWA6x#ȇx$A(?y' !` 7bBІ 0FH ?zׇB~_uk[].)k_)6p z,QS$SnD0RJ5L/5%i2"tUpWw EtDs@Fvl IUұ(tiDf $ǠdZÇyJCV.BA4¤`P;s2sOS&d7.`|Mh `yP0‰xp[+5x6ɛ"W`-@͈ m1{2{ppÝ;:䝢6olrH 0$ȄL0lCbyHMitCH%!df)sW)k몐}KQ"P!J$~"y6>͇xwE7Zćglj 5,tzxG,H"u w0p"TmyL(GwPfio D e|bv4t-nb1iXY-@$r`CDs!o)C0={%acJ c쮀ocogwn3Fw3@p\M&"_{-N{"ܴ~)Bt=aAR!rFTGs\QDZ?OPZXEASe;V E<" 8Ne1Ln,D܌m.$F<xO`>)!`FĶÅ nJӽF8 }̞ p܀n:U$6щxwP3P) vAP@'f[ `6D"q U3ǨGF RO1 !MG'UN.o$sa;56HIx|}y۲VksXg.֫D7L M"u/ m2`J@XXĬ[8;BLл&wT-fU#@"R?WO$L>ʿƨ ͇݉𯒈0YC( coC9hjF!w`phUL8GV>@D4)Bnao(k,#NnF mKnEL Rѣ=,XEm&دxb1"`__/ B1wz'N>tD 4(w#Q!=b\j @}ho?8k8 )g:[۶޽O~EzU!Bbt Ƅ[2#D-E)f((F"uSev4&jok[hEd_&><ȻO pov]! SPy>My&`pG_ì #80Yv%:"@oTu+!%0&n0ܭ!Ю{Raވ͈ŽC|mԀm. !$wstwy琥WwpSiGbq3^Wu@zG0¾bDD2@Gypj6^J=P hp!;1'F,q"Px9 DXWCo>B}0`#b}y X,jv'xG fn } aS@ EFBBI >oǨlXkvQ3 36!x XJ\LxC1fFaӃ ecݡ |`@eSD?n϶b -]ٶ]8m ܣZ^k` 3dB Nl@#e:(#@~;&^. D̚U䀐ADd;}k<TψiDZ4v"rTGB"6sf1KpbuOc;@h8>"x $B3Yg d3a()RHFĴQApZו`hM|!Hv8=R00x"k k@t<@ƀ.6Du0EC"&|&% ͇SDLtoݼ}U;vu\KK ګ9F Y!$!o+9ַ'"|@/_;v B`bj }TA;EB?y;-"T)bd5@= $Apc`0}F˸_e<}_(w#TwU8]7oMlhvR*a{V&C|BiawBވ0d{{潵b/FIBNf#hCh=iEj{LwcXNE$<?)b@v0 ݱpܧ>R 4??Gîm]/^o lͶ5t]U3$ݚ)q"ݽt o/?>~3MF,{Ѽ* f QV3" }'F.S@F" P4<)dPNjol@đuX(ܑ8}8 rᙇQD*;<` _1Enn"_''c8f'|& Cfa;p0Z!P(baG*ǰmZov T$DrlLzY+\ zzͫTt1@rU!K_<1, ,Ucq!DWP?BOP.tEXtcomment CREATOR: GIMP PNM Filter Version 1.1 8IENDB`pilkit-2.0/tests/assets/cat.gif0000664000175000017500000006514013051561165017052 0ustar venkovenko00000000000000GIF89add333fff! NETSCAPE2.0! ,dd H  *\ȰCJx0!ŋ"cE n 2"ɓ Ȗ$X L51"gNR%OD pЖ 8TԧөH`JʴBdYn9@o ŚԻ[֬ҽ& p pa]"$l.R3R@䭌5/e{y Z4[[7~tAbм 0j6nk&-@amg͝y/EJ9 _xfq㹃/\}}rf=7G7b'`Di"piـ~ h]h@X ҆ItaOvʱ|0pՕtݵJ72(F65)g4y_N!dT\F$Hz|'feo2yf[e.`[Vie}Rq/ْp*6wxd< (D%JVfFubxFeqI瓕g'x驜e*]ꬒ"YXړkנz"buw+䠈6{'WiFhhj>ZviB&e,bK\WY ."zPyG^{^rr۟Ljū&IQp,&Ơ c9\iE(򓠥cȧbT*l3 ]P+|3ʗyr^5\drġT5,cLGZ4gmAvsX^lʄ\닗X6Gjv^VAUîvG7oruu'u֞TPmPU2=ogXZqזzqM}oծq nڪV_[e6"\JwsAkZ--rQF9ެޓG 67};rU]8Q NK{ԒM 'h#W겪n5Gb^aPUp尃^5?Ra]QEU,ݬ>LO2MVȆ)U4Dqr|r yM]-v{OJƢXXL [sziUV{ q)Sqe%bZayg؞ɕE(fbrӢw]qBIVv$*Z,ZB^JbɛZOb^glhYy,N{ⴘtY5Wb'Y~jl%c:^֭euUny^nk|6 n%)׭Ե&1vc:|kVw)f8V]35 b; J,3T@j;_EEB)W[M!W)z2g3N\Ygm ^^V_PurPD&Ym\Ln"V#gCUH*Պզ(%Zɬ 16|SxcBJЂ.IQҕ Ϧ`8ޏNQXV<0m%]!#OZkעصJ3UN_S2WgfژZX{-M9WM.SR^6C}3MHkm"7JdQn8}\ڑW;$bΫ%smV99K4}E5f};R4NK.o`! ,dd H  *\ȰCJx0!ŋ"cE n 2"ɓ Ȗ$X J51"@3Lt'Ρ lQhKE*RSMh h`FAdYn9@o z5%[x׫YhZaW0\+N{6@ߍkTZo~ԲT+5щMk}8Xԩ^ f+E.@(w7|siϝzk*<:g-jfp/:+֯|zهT{\wXqytwLuQVrwk@ 8dea"w_+UMP@]vbs hٌ>՘Jgu%yVjٛvFy!mAbrQQuV2xLn dUo2}&g[ eeǦ]i%gd'іT8a5砀[dy_S]^k8&YV`Fִ]]n.נj$Jj^eEdxzuuҕeIqŊ[i)~m젠p&ļ8[:oqK(H@/Pku>('\Fmܾ'FwnbT_3FvF&\Twrm'6V]RvZFm BZѐY"mxZ *s%Vw㝭{KءLq~*mܡK{W_큫[+%c OwEVf9 cPAmc-k١.;T Rwt"<+)N-E 3cύ.qEɐdXE1ٖjԢ'XRDL$BeG ΒΩȄ%=!5讶O>t Я-j8!xiP.*<]ޱ`=*dZS,MjJhώfO :EI4X1 6 ֨9ӬdeH׭Xᳳr;cՐ8l >zo^+-mUX_JV{R$nV2zV\D_28meٟ$6QOe6&% oz9UW#Nsƕb`Λ ˔[. ͫWO&}on)_q0Kx~9/$ ! d,dd H  *\ȰCJx0!ŋ"cE n 2"ɓ Ȗ$X L51"gNR%OD pЖ 8TԧөH`JʴBdYn9@o ŚԻ[֬ҽ& p pa]"$l.R3R@䭌5/e{y Z4[[7~tAbм 0j6nk&-@amg͝y/EJ9 _xfq㹃/\}}rf=7G7b'`Di"piـ~ h]h@X ҆ItaOvʱ|0pՕtݵJ72(F65)g4y_N!dT\F$Hz|'feo2yf[e.`[Vie}Rq/ْp*6wxd< (D%JVfFubxFeqI瓕g'x驜e*]ꬒ"YXړkנz"buw+䠈6{'WiFhhj>ZviB&e,bK\WY ."zPyG^{^rr۟Ljū&IQp,&Ơ c9\iE(򓠥cȧbT*l3 ]P+|3ʗyr^5\drġT5,cLGZ4gmAvsX^lʄ\닗X6Gjv^VAUîvG7oruu'u֞TPmPU2=ogXZqזzqM}oծq nڪV_[e6"\JwsAkZ--rQF9ެޓG 67};rU]8Q NK{ԒM 'h#W겪n5Gb^aPUp尃^5?Ra]QEU,ݬ>LO2MVȆ)U4Dqr|r yM]-!>LĂ1)``eDVIv<gm5w.I`՝2Fqg@T٨1l. ^8j[\Wv79J@UNZBF\(*ęR1ehť'FyZY$rlS#ְ>(ѐtY+YTǤ XceSaPHԗ!(^lɲ>U&)+3CX!^+ȻlҮO.BzzsL<izD99E 2BQ9UP"KHEm qlY7YԹQ{WULf eAy[KmlDL?abmh`bьbVzl\ wyqS\PXmZviB&e,bK\WY ."zPyG^{^rr۟Ljū&IQp,&Ơ c9\iE(򓠥cȧbT*l3 ]P+|3ʗyr^5\drġT5,cLGZ4gmAvsX^lʄ\닗X6Gjv^VAUîvG7oruu'u֞TPmPU2=ogXZqזzqM}oծq nڪV_[e6"\JwsAkZ--rQF9ެޓG 67};rU]8Q NK{ԒM 'h#W겪n5Gb^aPUp尃^5?Ra]QEU,ݬ>LO2MVȆ)U4Dqr|r yM]-="#d:5˅֖kG#[ W{W?Eu*D՝rfZ{&SBPBItU6Q%o[75xfy^J$7{\ZZUkd&*qZ wn:`)C&9)f,}ק+g oF4 /?lfd{د04kK%Z lZRYv䇼g$yq_Ejj_W(#*O1V34.Wi P1my si`K$Wօ3%rOyt`U*$$UuMPNNh$qPTpi'mgDz;/K4%piL;q*+L?c9AT2jĴ\.s9/J$@^g ɱBT*vce1ʪx"JҔlgD'DB&4pM4j,;"vH,rpʐ,I͊KhD0'ɴ;QI5T G`R+g2D#' ѤBPZN5$xϝ1*X-2(IcVQEx8(שvfN߃*PsG;9У᯦:AldřJnBc_ "k2m&nFm$nV'tM!lH6" ,FR10XHo)u q2fIlD\lkEFmucrҒD ! ,dd H  *\ȰCJx0!ŋ"cE n 2"ɓ Ȗ$X L51"gNR%OD pЖ 8TԧөH`JʴBdYn9@o ŚԻ[֬ҽ& p pa]"$l.R3R@䭌5/e{y Z4[[7~tAbм 0j6nk&-@amg͝y/EJ9 _xfq㹃/\}}rf=7G7b'`Di"piـ~ h]h@X ҆ItaOvʱ|0pՕtݵJ72(F65)g4y_N!dT\F$Hz|'feo2yf[e.`[Vie}Rq/ْp*6wxd< (D%JVfFubxFeqI瓕g'x驜e*]ꬒ"YXړkנz"buw+䠈6{'WiFhhj>ZviB&e,bK\WY ."zPyG^{^rr۟Ljū&IQp,&Ơ c9\iE(򓠥cȧbT*l3 ]P+|3ʗyr^5\drġT5,cLGZ4gmAvsX^lʄ\닗X6Gjv^VAUîvG7oruu'u֞TPmPU2=ogXZqזzqM}oծq nڪV_[e6"\JwsAkZ--rQF9ެޓG 67};rU]8Q NK{ԒM 'h#W겪n5Gb^aPUp尃^5?Ra]QEU,ݬ>LO2MVȆ)U4Dqr|r yM]-v{OJƢXXL [sziUV{ q)Sqe%bZayg؞ɕE(fbrӢw]qBIVv$*Z,ZB^JbɛZOb^glhYy,N{ⴘtY5Wb'Y~jl%c:^֭euUny^nk|6 n%)׭Ե&1vc:|kVw)f8V]35 b; J,3T@j;_EEB)W[M!W)z2g3N\Ygm ^^V_PurPD&Ym\Ln"V#gCUH*Պզ(%Zɬ 16|SxcBJЂ.IQҕ Ϧ`8ޏNQXV<0m%]!#OZkעصJ3UN_S2WgfژZX{-M9WM.SR^6C}3MHkm"7JdQn8}\ڑW;$bΫ%smV99K4}E5f};R4NK.o`! ,dd H  *\ȰCJx0!ŋ"cE n 2"ɓ Ȗ$X J51"@3Lt'Ρ lQhKE*RSMh h`FAdYn9@o z5%[x׫YhZaW0\+N{6@ߍkTZo~ԲT+5щMk}8Xԩ^ f+E.@(w7|siϝzk*<:g-jfp/:+֯|zهT{\wXqytwLuQVrwk@ 8dea"w_+UMP@]vbs hٌ>՘Jgu%yVjٛvFy!mAbrQQuV2xLn dUo2}&g[ eeǦ]i%gd'іT8a5砀[dy_S]^k8&YV`Fִ]]n.נj$Jj^eEdxzuuҕeIqŊ[i)~m젠p&ļ8[:oqK(H@/Pku>('\Fmܾ'FwnbT_3FvF&\Twrm'6V]RvZFm BZѐY"mxZ *s%Vw㝭{KءLq~*mܡK{W_큫[+%c OwEVf9 cPAmc-k١.;T Rwt"<+)N-E 3cύ.qEɐdXE1ٖjԢ'XRDL$BeG ΒΩȄ%=!5讶O>t Я-j8!xiP.*<]ޱ`=*dZS,MjJhώfO :EI4X1 6 ֨9ӬdeH׭Xᳳr;cՐ8l >zo^+-mUX_JV{R$nV2zV\D_28meٟ$6QOe6&% oz9UW#Nsƕb`Λ ˔[. ͫWO&}on)_q0Kx~9/$ ! ,dd H  *\ȰCJx0!ŋ"cE n 2"ɓ Ȗ$X J51"@3Lt'Ρ lQhKE*RSMh h`FAdYn9@o z5%[x׫YhZaW0\+k6эkTZo~4^>5щWk}8Xԩ43mͶW]@Qܹ59yE<]Kwut=s _;W+}o٧y퇐wpwWu;1l;Umv-^ µ!47׃ɥx 2q}bW%a7R`(YS"wgM&b*hXg[i%g%7Q朊zCz'gٹg\E_dUdi*ueFP`aפ)8(Yx7@>U(ya)k%Obeta)Tw:xѦgլf^2{jTu4&`f1YYjZbPnbf›ONtܮqVFݸVH,~ lWmTz /PmF\ Īhqʔ(ŭEr+[) o̥ JDMQra|j?bi8FW]TtFZaV<$&v9 mie=^VZҩEr;Ze3#Ε oJ-F+dDSqo~ ̑X[[zMYޘ'm~Zؕ`]>/%khS"OH2PQeS &ʘq5XY5]Kڪ[ziq5q_'7t0L>i[y8J~qhW My$bieqXg6P&6I\&4۶ƵKy 2E?S[װsHMV^ڢ2$-r {ă;hgB6 -Z?b urEj(W^v' mh#e<&a!Y{y%/ `Z͗XXEIWtMlc nIκUSeL;[ɬfq >Z$%uZO (ϣ1f5An|%OxкmsR/BpR 1Cυ3ڗ5T&e4! XNXbE'DrVeʨJSd@c&q41V!q <t)24:M[!ԒT @];Y)P=t.4ZZ֍eA:WFF0q]̱X:ԫjUhE¿ PUL)+ւr1,4ZU#l)7a hµFa}oGr)amDN5ҡߤXvkݖ@6ԓ'F Ӗ?Ym/ʷ$y g7f>$1kHbi!XK! ,dd H  *\ȰCJx0!ŋ"cE n 2"ɓ Ȗ$X J51"@3Lt'Ρ lQhKE*RSMh h`FAdYn9@o z5%[x׫YhZaW0\+k6эkTZo~4^>5щWk}8Xԩ43mͶW]@Qܹ59yE<]Kwut=s _;W+}o٧y퇐wpwW} qjUm;v2^ µ!7׃Ec ߋ!eٌgUnufczb/VdS"wgQԌ($R"V2x28TBnYdT97dY.g$XZWkzRmgOEءnUETA(YZV_Q-vfWRjnouhMbGkQ*st}SS_e*vo];֕Qv[ #JN] Z[ٗ~{Zɚ6|e,`mRa^箷xi{]='yѺߢo6)|׬{*r!nJ\sхe/dIװ2Yv2@-ܴn_=hU[AG T+|* ةi5-]U)cW?L`T+ɦ?FuPUDb^xkMVGW 7@`y؟?7{qNک5$Bm )5'\lS9Ydvy[=Ý'Bo92%V;}q'\kTPY_D&/: 2,Nj\SWBqPV|fa7h!ZN%s[LTn~{ZG@ѯnQbv֝yWڨUn ZSppvSJ-4,= p#{\EAݒ˜J gDOh)dK5j'5Ңխ*h:-*v*5E .Әsu*ĚUe$'(*ΞR}ev҅/g'X|t9jG,[LN !WqV'Un$&}M:y&7/)M&EfK("/f{cBVw%ߧ[ " <3$ ! ,dd H  *\ȰCJx0!ŋ"cE n 2"ɓ Ȗ$X J51"@3Lt'Ρ lQhKE*RSMh h`FAdYn9@o z5%[x׫YhZaW0\+k6эkTZo~4^>5щWk}8Xԩ43mͶW]@Qܹ59<]Kwut+cgj٪oou+o>AW!wp}gWu;1l;UmvS|pYhmz0ք \45cؕ}4Ɓ(Dcg8w|5wgBěb*hXg[i%\G4dT9Jg`i^Ie~Ŧda=ybg\\d99bcb"Rjd|nouw%[15ddqdwꮼ ^SܛkYKAET"۫.*TC9Un:i؎zPyay>1b«lB//{qp+{ښ\{>*+Z>՚ϩ)2kt]\!m`8,iZ(c{fc:UT7#@b>lڠ<,\T7VE$]=ӝ39?ZAǻR216Wcƕ\,/{H3X3cU@sPĖw* qmqO3F`l 2OƼY@Pm^D]ZZ{zhCJ}[,֘E+3":$s yϙs]dmQܮW͞}9!~ |p>rɏkYnm4sP|~.s=dQ`_D6%*3Ym#7WZ1p!|8"Y8fzWZȅYn4pS?:1K]v YZԆ;(ڬs|V5\*SW&]|zW&ɤQSس)]luI=]\ IЇ]ȒE>RiyNLV%DLV/;Kf~8ˉpb$DGLVuz$'k-mO]se49O]z.f3R®"3teW*MҦyͮ9 ʯ֔51.?zy&LJE=,U[x"D3sB7i/rYW3g5ݖMS&C"J"o3[6sIPjr'&I"S(7Q,ek,P 'ʋׄ@P1h9u6jFwQ_J ҳ4&I'PS)kAe:OWԩ=էG3Ԥ597ԣjLRUM|ZV6˯C-k6Ѥ`&<_ft5jbhXrE܀[DEC(YΚB\-,:ӳxWMIl7)Yj3CIDvkvgnK[2gTM:\fdXΚ%دh'nly7Nvw$ ߘ[֭o|?&.+crۇKF qa! ,dd H  *\ȰCJx0!ŋ"cE n 2"ɓ Ȗ$X J51"@3Lt 'Ρ lQhKE*RSMh h`FAdYn9@o z5%[x׫YhZaW0\+k6эk4޹o~/׿I=`Īvxi_U:{s{ P6nN>~09s (EM?~x%yÞW4yz߿MO},`{iWFcǟ6zTr`Yw fZ5h]'!B!·!s-ۊXysTj*}yą]F&f|7ATieZg;ȗb*%i؄[i%d\P^&_1V`Qgdai搬QV>U{9yҝor]eJfULIנd eNI*{jIu+r!+zjPkn)Jtd1Ր*yz*V{ZLN f^4k.JVʫޜ2yﮌV}9pY/iZn ջJE$|:nlﶺhc,$wO;) /JLDdVdQ9*tɝY"*v3Pu,Ļԣ2'ȉ-o8}ldьmWkMin:獪TT"%*>TmxP9aV"2~TzMla8Bn&^{m[LZ jhEbύlrvf(dgUfsAϺ*3fJ\}?rqYU]"5bbi>a޻u*sgf=#JCB# 2[ qŚW Q7ʬz ?N9 bfAA@ꃣS; eBjĒOt,`yެ$O50jSJh&DŃUWnu\'oͅlKhc䪹ElI _ C5e9ɉOL2,[/ۙ 0 c@Z'`?#'(eMU6iM$:V2,/}Qdf0͠3Kť$ZY3А;jCiQXVQ2oS¤ g|1hlmzTqJՌf* ƌΈ)&G3 Os*VddWh 2vYic:!iU'cZXfX_3Ӳ JHDX]e1Sg˭n#QXgcZ* ZD tsTeMuCas ^75b. f$ ! ,dd H  *\ȰCJx0!ŋ"cE n 2"ɓ Ȗ$X J51"@3Lt 'Ρ lQhKE*RSMh h`FAdYn9@o z5%[x׫YhZaW0\+k6эk4޹o~/׿I=`Īvxi_U:{s{ P6nN>^3s (EM?~x%yÞW<\ߦ̯o0|ŕ~!{߁EJ-NEU`{yR]CYP|pyV ¥aXy(!"']Wg 0\+qb GFyeܐ2bFe(\PAWmbhX[i%b)XHLAFszuVVd٘Yziibf\XdyYɝZZt j{uVftnWx"tWjTQSDg٭ lNU’([ʦV4j]>K"է߾Jjæk]-kLت+/{.U{) prcr1.ܰRi_ڤ5P!{核+m%piyrjWq[Nkf`1A&AgةiVơ)lA&^+k2)h\Ó$u+︤AvP؜qP/nV)sUjqY(M@fՃz.lIȚJYhfiSX&qA\i<^^tWM;iM.J*dbʵz-ᦂ}^@:C!n:N:ĵ3q;.z̟Emj˗fgjiX/ww \hB&GckKbM9#XT@F.C#a\ASIs BZ^K+/*6c uoZ_dVOvZ*ljN%s̀+KJ}bשD9-SBcA#ZFE,J:D"XnN#V㰈u ǢH5]}BV1O$Hig)HG6DcLW*Y>-H R$$STՙlFXxGOfReSWV'z3dIIRfR/ nɚ|uU<#ˉuN 8mQ̋0Im3*z%[b|9m&d=ii̪2]0ܬ ȦLL/s͕ ӑX#c Qf|(0"LIY~,K=&8:YLL)NiKzդGRk('5-J@uCdIhj4v5S`1LTi*QJPud'u7BM&XR?8͆u$  ɱt= ]&Q?))י~k/9 .\R^"l.J׹SDqC ! ,dd H  *\ȰCJx0!ŋ"cE n 2"ɓ Ȗ$X J51"@3Lt 'Ρ lQhKE*RSMh h`FAdYn9@o z5%[x׫YhZaW0\+k6эk4޹o~/׿I=`Īvxi_U:{s{ P6nN>^3s (EM?~x%yÞW<\ߦ̯o0|ŕ~!{߁EJ-NEU`{yR]CYP|pyV ¥aXy(!"']Wg 0\+qb GFyeܐ2bFe(\PAWmbhX[i%b)XHLAFszuVVd٘Yziibf\XdyYɝZZt j{uVftnWx"tWjTQSDg٭ lNU’([ʦV4j]>K"է߾Jjæk]-kLت+/*F*/[z^q{())[wkd[mk饷yIUipIvei~KW- KL4,X&/]XigS>wq*YLhKuD>Yf9<];֨j3 .;_K83T% r(XW@?}, iSHT3E7ЂW O(jG Z$*jI O0꒴xFN9ZvEyk;Y.satkyIcd e=vpA[՜)7&_YƳ?"15H.KF<&ԛvVU$+DյTe/-/ËȢijrd I.3.Bi/y/ʒ)YZTRFq.L^{Jw-VU kK^;pilkit-2.0/tests/test_utils.py0000664000175000017500000000440013051561165017053 0ustar venkovenko00000000000000import os from io import UnsupportedOperation from pilkit.exceptions import UnknownFormat, UnknownExtension from pilkit.lib import Image from pilkit.utils import (extension_to_format, format_to_extension, FileWrapper, save_image, prepare_image, quiet) from mock import Mock, patch from nose.tools import eq_, raises, ok_ from tempfile import NamedTemporaryFile from .utils import create_image def test_extension_to_format(): eq_(extension_to_format('.jpeg'), 'JPEG') eq_(extension_to_format('.rgba'), 'SGI') def test_format_to_extension_no_init(): eq_(format_to_extension('PNG'), '.png') eq_(format_to_extension('ICO'), '.ico') @raises(UnknownFormat) def test_unknown_format(): format_to_extension('TXT') @raises(UnknownExtension) def test_unknown_extension(): extension_to_format('.txt') def test_default_extension(): """ Ensure default extensions are honored. Since PIL's ``Image.EXTENSION`` lists ``'.jpe'`` before the more common JPEG extensions, it would normally be the extension we'd get for that format. ``pilkit.utils.DEFAULT_EXTENSIONS`` is our way of specifying which extensions we'd prefer, and this tests to make sure it's working. """ eq_(format_to_extension('JPEG'), '.jpg') @raises(AttributeError) def test_filewrapper(): class K(object): def fileno(self): raise UnsupportedOperation FileWrapper(K()).fileno() def test_save_with_filename(): """ Test that ``save_image`` accepts filename strings (not just file objects). This is a test for GH-8. """ im = create_image() outfile = NamedTemporaryFile() save_image(im, outfile.name, 'JPEG') outfile.close() def test_format_normalization(): """ Make sure formats are normalized by ``prepare_image()``. See https://github.com/matthewwithanm/django-imagekit/issues/262 """ im = Image.new('RGBA', (100, 100)) ok_('transparency' in prepare_image(im, 'gIF')[1]) def test_quiet(): """ Make sure the ``quiet`` util doesn't error if devnull is unwriteable. See https://github.com/matthewwithanm/django-imagekit/issues/294 """ mocked = Mock(side_effect=OSError) with patch.object(os, 'open', mocked): with quiet(): pass pilkit-2.0/tests/__init__.py0000664000175000017500000000000013051561165016403 0ustar venkovenko00000000000000pilkit-2.0/setup.py0000664000175000017500000000336613051561165014664 0ustar venkovenko00000000000000#/usr/bin/env python import codecs import os from setuptools import setup, find_packages # Workaround for multiprocessing/nose issue. See http://bugs.python.org/msg170215 try: import multiprocessing except ImportError: pass read = lambda filepath: codecs.open(filepath, 'r', 'utf-8').read() # Load package meta from the pkgmeta module without loading the package. pkgmeta = {} pkgmeta_file = os.path.join(os.path.dirname(__file__), 'pilkit', 'pkgmeta.py') with open(pkgmeta_file) as f: code = compile(f.read(), 'pkgmeta.py', 'exec') exec(code, pkgmeta) setup( name='pilkit', version=pkgmeta['__version__'], description='A collection of utilities and processors for the Python Imaging Libary.', long_description=read(os.path.join(os.path.dirname(__file__), 'README.rst')), author='Matthew Tretter', author_email='m@tthewwithanm.com', license='BSD', url='http://github.com/matthewwithanm/pilkit/', packages=find_packages(exclude=['tests', 'tests.*']), zip_safe=False, include_package_data=True, tests_require=[ 'mock>=1.0.1', 'nose>=1.3.6', 'nose-progressive>=1.5.1', 'Pillow', ], test_suite='nose.collector', install_requires=[], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Topic :: Utilities' ], ) pilkit-2.0/README.rst0000664000175000017500000000576113051561165014642 0ustar venkovenko00000000000000PILKit is a collection of utilities for working with PIL (the Python Imaging Library). One of its main features is a set of **processors** which expose a simple interface for performing manipulations on PIL images. Looking for more advanced processors? Check out `Instakit`_! **For the complete documentation on the latest stable version of PILKit, see** `PILKit on RTD`_. .. image:: https://api.travis-ci.org/matthewwithanm/pilkit.png :target: https://travis-ci.org/matthewwithanm/pilkit .. _`PILKit on RTD`: http://pilkit.readthedocs.org .. _`Instakit`: https://github.com/fish2000/instakit Installation ============ 1. Install `PIL`_ or `Pillow`_. 2. Run ``pip install pilkit`` (or clone the source and put the pilkit module on your path) .. note:: If you've never seen Pillow before, it considers itself a more-frequently updated "friendly" fork of PIL that's compatible with setuptools. As such, it shares the same namespace as PIL does and is a drop-in replacement. .. _`PIL`: http://pypi.python.org/pypi/PIL .. _`Pillow`: http://pypi.python.org/pypi/Pillow Usage Overview ============== Processors ---------- The "pilkit.processors" module contains several classes for processing PIL images, which provide an easy to understand API: .. code-block:: python from pilkit.processors import ResizeToFit img = Image.open('/path/to/my/image.png') processor = ResizeToFit(100, 100) new_img = processor.process(img) A few of the included processors are: * ``ResizeToFit`` * ``ResizeToFill`` * ``SmartResize`` * ``Adjust`` * ``TrimBorderColor`` * ``Transpose`` There's also a ``ProcessorPipeline`` class for executing processors sequentially: .. code-block:: python from pilkit.processors import ProcessorPipeline, ResizeToFit, Adjust img = Image.open('/path/to/my/image.png') processor = ProcessorPipeline([Adjust(color=0), ResizeToFit(100, 100)]) new_image = processor.process(img) Utilities --------- In addition to the processors, PILKit contains a few utilities to ease the pain of working with PIL. Some examples: ``prepare_image`` Prepares the image for saving to the provided format by doing some common-sense conversions, including preserving transparency and quantizing. ``save_image`` Wraps PIL's ``Image.save()`` method in order to gracefully handle PIL's "Suspension not allowed here" errors, and (optionally) prepares the image using ``prepare_image`` Utilities are also included for converting between formats, extensions, and mimetypes. Community ========= Please use `the GitHub issue tracker `_ to report bugs. `A mailing list `_ also exists to discuss the project and ask questions, as well as the official `#imagekit `_ channel on Freenode. (Both of these are shared with the `django-imagekit`_ project—from which PILKit spun off.) .. _`django-imagekit`: https://github.com/jdriscoll/django-imagekit pilkit-2.0/PKG-INFO0000664000175000017500000001111113051563625014235 0ustar venkovenko00000000000000Metadata-Version: 1.1 Name: pilkit Version: 2.0 Summary: A collection of utilities and processors for the Python Imaging Libary. Home-page: http://github.com/matthewwithanm/pilkit/ Author: Matthew Tretter Author-email: m@tthewwithanm.com License: BSD Description: PILKit is a collection of utilities for working with PIL (the Python Imaging Library). One of its main features is a set of **processors** which expose a simple interface for performing manipulations on PIL images. Looking for more advanced processors? Check out `Instakit`_! **For the complete documentation on the latest stable version of PILKit, see** `PILKit on RTD`_. .. image:: https://api.travis-ci.org/matthewwithanm/pilkit.png :target: https://travis-ci.org/matthewwithanm/pilkit .. _`PILKit on RTD`: http://pilkit.readthedocs.org .. _`Instakit`: https://github.com/fish2000/instakit Installation ============ 1. Install `PIL`_ or `Pillow`_. 2. Run ``pip install pilkit`` (or clone the source and put the pilkit module on your path) .. note:: If you've never seen Pillow before, it considers itself a more-frequently updated "friendly" fork of PIL that's compatible with setuptools. As such, it shares the same namespace as PIL does and is a drop-in replacement. .. _`PIL`: http://pypi.python.org/pypi/PIL .. _`Pillow`: http://pypi.python.org/pypi/Pillow Usage Overview ============== Processors ---------- The "pilkit.processors" module contains several classes for processing PIL images, which provide an easy to understand API: .. code-block:: python from pilkit.processors import ResizeToFit img = Image.open('/path/to/my/image.png') processor = ResizeToFit(100, 100) new_img = processor.process(img) A few of the included processors are: * ``ResizeToFit`` * ``ResizeToFill`` * ``SmartResize`` * ``Adjust`` * ``TrimBorderColor`` * ``Transpose`` There's also a ``ProcessorPipeline`` class for executing processors sequentially: .. code-block:: python from pilkit.processors import ProcessorPipeline, ResizeToFit, Adjust img = Image.open('/path/to/my/image.png') processor = ProcessorPipeline([Adjust(color=0), ResizeToFit(100, 100)]) new_image = processor.process(img) Utilities --------- In addition to the processors, PILKit contains a few utilities to ease the pain of working with PIL. Some examples: ``prepare_image`` Prepares the image for saving to the provided format by doing some common-sense conversions, including preserving transparency and quantizing. ``save_image`` Wraps PIL's ``Image.save()`` method in order to gracefully handle PIL's "Suspension not allowed here" errors, and (optionally) prepares the image using ``prepare_image`` Utilities are also included for converting between formats, extensions, and mimetypes. Community ========= Please use `the GitHub issue tracker `_ to report bugs. `A mailing list `_ also exists to discuss the project and ask questions, as well as the official `#imagekit `_ channel on Freenode. (Both of these are shared with the `django-imagekit`_ project—from which PILKit spun off.) .. _`django-imagekit`: https://github.com/jdriscoll/django-imagekit Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Topic :: Utilities pilkit-2.0/MANIFEST.in0000664000175000017500000000017313051561165014701 0ustar venkovenko00000000000000include AUTHORS include LICENSE include README.rst recursive-include docs * recursive-include tests *.py *.gif *.png *.jpg pilkit-2.0/setup.cfg0000664000175000017500000000007313051563625014766 0ustar venkovenko00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 pilkit-2.0/AUTHORS0000664000175000017500000000217013051562442014211 0ustar venkovenko00000000000000Maintainers ----------- * `Bryan Veloso`_ * `Matthew Tretter`_ * `Chris Drackett`_ * `Greg Newman`_ * `Venelin Stoykov`_ Contributors ------------ In addition to those listed on the `contributors page`__, the following people have also had a hand in bringing PILKit to life: * `Justin Driscoll`_ * `Timothée Peignier`_ * `Jan Sagemüller`_ * `Alexander Bohn`_ * `Eric Eldredge`_ * `Germán M. Bravo`_ * `Kevin Postal`_ * `Madis Väin`_ __ https://github.com/matthewwithanm/pilkit/graphs/contributors .. _Bryan Veloso: http://github.com/bryanveloso .. _Matthew Tretter: http://github.com/matthewwithanm .. _Chris Drackett: http://github.com/chrisdrackett .. _Greg Newman: http://github.com/gregnewman .. _Venelin Stoykov: http://github.com/vstoykov .. _Justin Driscoll: http://github.com/jdriscoll .. _Timothée Peignier: http://github.com/cyberdelia .. _Jan Sagemüller: https://github.com/version2 .. _Alexander Bohn: http://github.com/fish2000 .. _Eric Eldredge: http://github.com/lettertwo .. _Germán M. Bravo: http://github.com/Kronuz .. _Kevin Postal: https://github.com/kevinpostal .. _Madis Väin: https://github.com/madisvain