pax_global_header00006660000000000000000000000064134417123450014516gustar00rootroot0000000000000052 comment=8376c4a238ff725c29b435bdd5627e8e22a8d07a Trimage-1.0.6/000077500000000000000000000000001344171234500131125ustar00rootroot00000000000000Trimage-1.0.6/.gitignore000066400000000000000000000025031344171234500151020ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ pip-wheel-metadata/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ Trimage-1.0.6/COPYING000066400000000000000000000020611344171234500141440ustar00rootroot00000000000000Copyright (c) 2010 Kilian Valkhof, Paul Chaplin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Trimage-1.0.6/MANIFEST.in000066400000000000000000000002131344171234500146440ustar00rootroot00000000000000include COPYING MANIFEST MANIFEST.in README.md bin/trimage recursive-include desktop *.svg *.desktop recursive-include trimage/ *.py *.png Trimage-1.0.6/README.md000066400000000000000000000020561344171234500143740ustar00rootroot00000000000000# Trimage image compressor A cross-platform tool for optimizing PNG and JPG files. Trimage is a cross-platform GUI and command-line interface to optimize image files via [advpng](http://advancemame.sourceforge.net/comp-readme.html), [jpegoptim](http://www.kokkonen.net/tjko/projects.html), [optipng](http://optipng.sourceforge.net) and [pngcrush](https://pmt.sourceforge.io/pngcrush) depending on the filetype (currently, PNG and JPG files are supported). It was inspired by [imageoptim](http://imageoptim.pornel.net). All image files are losslessly compressed on the highest available compression levels. Trimage gives you various input functions to fit your own workflow: a regular file dialog, dragging and dropping and various command line options. ## Installation instructions Visit [Trimage.org](http://trimage.org) to install Trimage as a package. ## Building instructions ### Prerequisites - PyQt5 - advpng - jpegoptim - optipng - pngcrush ### Build from source Build and install by running: python setup.py build sudo python setup.py install Trimage-1.0.6/TODO.md000066400000000000000000000026071344171234500142060ustar00rootroot00000000000000# Todo - general refactoring - sys.exit(1) for errors -- how to handle? Not good to simply sys.exit() from any random part of code (can leave things in a mess) - consider context managers for handling compression, so as to keep operations atomic and/or rollback-able - add a recursive option on the command-line for use with -d - make -f accept a list of files - make the current verbose be "normal", and make -verbose print the commandline app prints as well - find a way to specify the version once for everywhere - notification area drag/drop widget -> probably need gtk for gnome - figure out how to make mac and win versions (someone else :) <- via gui2exe - animate compressing.gif - allow selection/deletion of rows from table (and subsequently the imagelist) - punypng api? http://www.gracepointafterfive.com/punypng/api - imagemagick/graphicsmagick? - always on top option - intelligently recompress, i.e. go through the list of files, recompress each until no more gains are seen (and a sensible number-of-tries limit isn't exceeded), and flag that file as fully-optimised. Repeat for each file in the list, until all are done. Saves pointlessly trying to optimise files. Consider the case of a directory of 100 files, already optimised once. Recompressing maximally compresses 90. Recompressing again would currently try to recompress all 100, when only 10 would be worthy of trying to compress further Trimage-1.0.6/bin/000077500000000000000000000000001344171234500136625ustar00rootroot00000000000000Trimage-1.0.6/bin/trimage000066400000000000000000000015521344171234500152400ustar00rootroot00000000000000#!/usr/bin/env python3 # #Copyright (c) 2010 Kilian Valkhof, Paul Chaplin, Tarnay Kálmán # #Permission is hereby granted, free of charge, to any person #obtaining a copy of this software and associated documentation #files (the "Software"), to deal in the Software without #restriction, including without limitation the rights to use, #copy, modify, merge, publish, distribute, sublicense, and/or sell #copies of the Software, and to permit persons to whom the #Software is furnished to do so, subject to the following #conditions: # #The above copyright notice and this permission notice shall be #included in all copies or substantial portions of the Software. import os, sys import subprocess import trimage if __name__ == "__main__": path = os.path.join(os.path.dirname(trimage.__file__), "trimage.py") subprocess.call([sys.executable, path] + sys.argv[1:]) Trimage-1.0.6/debian/000077500000000000000000000000001344171234500143345ustar00rootroot00000000000000Trimage-1.0.6/debian/changelog000066400000000000000000000052471344171234500162160ustar00rootroot00000000000000trimage (1.0.6-0ubuntu1) bionic; urgency=low * Migrate to python 3 and python-qt 5 * fix bug with hanging thread * remove hurry.filesize package -- Kilian Valkhof Tue, 12 Mar 2019 11:54:00 +0200 trimage (1.0.5-0ubuntu1) jaunty; urgency=low * prevent images from becoming larger after recompression * prevent images from being written when not writeable * allow dragging of entire directories * performance increases for large numbers of files -- Kilian Valkhof Sun, 26 Sep 2010 15:24:04 +0200 trimage (1.0.4-0ubuntu1) jaunty; urgency=low * prevent systemtray from starting if a CLI compression is done * preliminary OSX work -- Kilian Valkhof Fri, 17 Sep 2010 20:45:16 +0200 trimage (1.0.3-0ubuntu1) jaunty; urgency=low * fix for environments without supported system tray * add copyright info for filesize.py * update various debian configurations -- Kilian Valkhof Sat, 12 Jun 2010 14:04:35 +0200 trimage (1.0.2-0ubuntu1) jaunty; urgency=low * save window geometry and file directory upon closing * prettier systray menu * Switch to dpkg-source 3.0(quilt) format -- Kilian Valkhof Thu, 03 Jun 2010 13:01:30 +0200 trimage (1.0.1b2-0ubuntu1) jaunty; urgency=low * use a threadpool for images * more robust file handling * re-adding images now results in recompressing them * compressing message now shows filename * wider array of status messages in the table * add a notification area icon * remove BOM -- Kilian Valkhof Mon, 29 Mar 2010 13:08:18 +0200 trimage (1.0.1b-0ubuntu1) jaunty; urgency=low * use a threadpool for images * more robust file handling * re-adding images now results in recompressing them * compressing message now shows filename * wider array of status messages in the table * add a notification area icon -- Kilian Valkhof Mon, 29 Mar 2010 13:08:18 +0200 trimage (1.0.0b3-0ubuntu1) jaunty; urgency=low * better image filetype determination * use unicode strings everywhere * fix bug where Trimage would crash if a directory didn't end in a slash * avoid a segfault when parsing empty directories * ../../debian/changelog -- Kilian Valkhof Mon, 29 Mar 2010 13:08:18 +0200 trimage (1.0.0b2-0ubuntu1) jaunty; urgency=low * correct parsing of unicode characters in filenames * changelog -- Kilian Valkhof Sun, 28 Mar 2010 16:09:45 +0200 trimage (1.0.0b-0ubuntu1) jaunty; urgency=low * Trimage image compressor -- Kilian Valkhof Tue, 23 Mar 2010 20:18:17 +0100 Trimage-1.0.6/debian/compat000066400000000000000000000000031344171234500155330ustar00rootroot0000000000000010 Trimage-1.0.6/debian/control000066400000000000000000000015771344171234500157510ustar00rootroot00000000000000Source: trimage Section: graphics Priority: optional Maintainer: Kilian Valkhof Build-Depends: debhelper (>=7), python3 Standards-Version: 3.9.2 Homepage: http://trimage.org Package: trimage Architecture: all Depends: ${misc:Depends}, ${python:Depends}, python-pyqt5 (>=5.7), optipng (>=0.6.2.1), advancecomp (>=1.15), jpegoptim (>=1.2.2), pngcrush (>=1.6.7) Description: GUI and command-line interface to optimize image files Trimage is a cross-platform GUI and command-line interface to optimize image files via optipng, advpng, pngcrush and jpegoptim, depending on the filetype (currently, PNG and JPG files are supported). All image files are losslessly compressed on the highest available compression levels. Trimage gives you various input functions to fit your own workflow: A regular file dialog, dragging and dropping and various command line options. Trimage-1.0.6/debian/copyright000066400000000000000000000220451344171234500162720ustar00rootroot00000000000000This package was debianized by: Kilian Valkhof on Tue, 23 Mar 2010 20:18:17 +0100 Upstream Author: Kilian Valkhof Copyright: Copyright (C) 2010 Kilian Valkhof, Paul Chaplin License: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. The Debian packaging is: Copyright (C) 2010 Kilian Valkhof, Paul Chaplin and is licensed under the MIT license, see above. ThreadPool is: Copyright (c) Morten Holdflod Moeller License: GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. Trimage-1.0.6/debian/docs000066400000000000000000000000071344171234500152040ustar00rootroot00000000000000README Trimage-1.0.6/debian/pycompat000066400000000000000000000000021344171234500161030ustar00rootroot000000000000003 Trimage-1.0.6/debian/rules000077500000000000000000000000361344171234500154130ustar00rootroot00000000000000#!/usr/bin/make -f %: dh $@ Trimage-1.0.6/debian/source/000077500000000000000000000000001344171234500156345ustar00rootroot00000000000000Trimage-1.0.6/debian/source/format000066400000000000000000000000151344171234500170430ustar00rootroot000000000000003.0 (quilt) Trimage-1.0.6/desktop/000077500000000000000000000000001344171234500145635ustar00rootroot00000000000000Trimage-1.0.6/desktop/trimage.desktop000066400000000000000000000003401344171234500176030ustar00rootroot00000000000000[Desktop Entry] Name=Trimage image compressor Comment=A cross-platform tool for optimizing PNG and JPG files. Terminal=false Icon=trimage Type=Application Exec=trimage Categories=Application;Qt;Graphics; StartupNotify=true Trimage-1.0.6/desktop/trimage.svg000066400000000000000000000224461344171234500167440ustar00rootroot00000000000000 image/svg+xml Trimage-1.0.6/doc/000077500000000000000000000000001344171234500136575ustar00rootroot00000000000000Trimage-1.0.6/doc/trimage.1000066400000000000000000000017131344171234500153730ustar00rootroot00000000000000.\" Copyright (C) 2011 Kyrill Detinov .\" .\" This manual page is distributed under the terms .\" of the GNU Free Documentation License version 1.3. .\" .TH TRIMAGE "1" "2019-03-12" "trimage 1.0.6" "User Commands" .SH NAME trimage \- losslessly optimizing png and jpeg liles .SH SYNOPSIS .B trimage .RI [ options ] .SH DESCRIPTION Front\-end to compress png and jpeg images via optipng, advpng, pngcrush and jpegoptim. .SH OPTIONS .TP \fB\-d\fI directory\fR, \fB\-\-directory\fR=\fIdirectory\fR Compresses images in directory. .TP \fB\-f\fI filename\fR, \fB\-\-file\fR=\fIfilename\fR Compresses image. .TP \fB\-h\fR, \fB\-\-help\fR Show help message. .TP \fB\-q\fR, \fB\-\-quiet\fR Quiet mode. .TP \fB\-v\fR, \fB\-\-verbose\fR Verbose mode (default). .TP \fB\-\-version\fR Show program version number. .SH "SEE ALSO" .BR advpng (1), .BR jpegoptim (1), .BR opt-png (1), .BR opt-jpg (1), .BR pngcrush (1), .BR pngrecolor (1), .BR pngstrip (1) Trimage-1.0.6/resources/000077500000000000000000000000001344171234500151245ustar00rootroot00000000000000Trimage-1.0.6/resources/trimage.svg000066400000000000000000000224461344171234500173050ustar00rootroot00000000000000 image/svg+xml Trimage-1.0.6/resources/window.ui000066400000000000000000000170741344171234500170030ustar00rootroot00000000000000 Kilian Valkhof trimage 0 0 600 170 Trimage image compressor 0 0 true 1 1 0 0 0 0 0 10 9 PointingHandCursor Add file to the compression list &Add and compress list-add.pnglist-add.png Alt+A 8 QFrame::Plain Drag and drop images onto the table 1 10 Qt::Horizontal 498 20 9 PointingHandCursor Recompress all images &Recompress view-refresh.pngview-refresh.png Alt+R false true true Drag files in here Drag files in here QFrame::NoFrame QFrame::Plain 0 0 Qt::ScrollBarAlwaysOff true true QAbstractItemView::DropOnly true Qt::ElideRight true Qt::NoPen true false false false false 10 10 true true true Trimage-1.0.6/setup.py000066400000000000000000000023041344171234500146230ustar00rootroot00000000000000#!/usr/bin/env python3 from distutils.core import setup setup(name = "trimage", version = "1.0.6", description = "Trimage image compressor - A cross-platform tool for optimizing PNG and JPG files", author = "Kilian Valkhof, Paul Chaplin", author_email = "help@trimage.org", url = "http://trimage.org", license = "MIT license", packages = ["trimage", "trimage.ThreadPool"], package_data = {"trimage" : ["pixmaps/*.*"] }, data_files=[('share/icons/hicolor/scalable/apps', ['desktop/trimage.svg']), ('share/applications', ['desktop/trimage.desktop']), ('share/man/man1', ['doc/trimage.1'])], scripts = ["bin/trimage"], long_description = """Trimage is a cross-platform GUI and command-line interface to optimize image files via advpng, jpegoptim, optipng and pngcrush, depending on the filetype (currently, PNG and JPG files are supported). It was inspired by imageoptim. All image files are losslessy compressed on the highest available compression levels. Trimage gives you various input functions to fit your own workflow: A regular file dialog, dragging and dropping and various command line options.""", requires = ["PyQt5"] ) Trimage-1.0.6/trimage/000077500000000000000000000000001344171234500145425ustar00rootroot00000000000000Trimage-1.0.6/trimage/ThreadPool/000077500000000000000000000000001344171234500166035ustar00rootroot00000000000000Trimage-1.0.6/trimage/ThreadPool/ThreadPool.py000066400000000000000000000215141344171234500212210ustar00rootroot00000000000000#!/usr/bin/env python3 ''' ThreadPool Implementation @author: Morten Holdflod Moeller - morten@holdflod.dk @license: LGPL v3 ''' from __future__ import with_statement from threading import Thread, RLock from time import sleep from queue import Queue, Empty import logging import sys class NullHandler(logging.Handler): def emit(self, record): pass h = sys.stderr logging.getLogger('threadpool').addHandler(h) logging.getLogger('threadpool.worker').addHandler(h) class ThreadPoolMixIn: """Mix-in class to handle each request in a new thread from the ThreadPool.""" def __init__(self, threadpool=None): if (threadpool == None): threadpool = ThreadPool() self.__private_threadpool = True else: self.__private_threadpool = False self.__threadpool = threadpool def process_request_thread(self, request, client_address): """Same as in BaseServer but as a thread. In addition, exception handling is done here. """ try: self.finish_request(request, client_address) self.close_request(request) except: self.handle_error(request, client_address) #IGNORE:W0702 self.close_request(request) def process_request(self, request, client_address): self.__threadpool.add_job(self.process_request_thread, [request, client_address]) def shutdown(self): if (self.__private_threadpool): self.__threadpool.shutdown() class AddJobException(Exception): ''' Exceptoion raised when a Job could not be added to the queue ''' def __init__(self, msg): Exception.__init__(self, msg) class ThreadPool: ''' The class implementing the ThreadPool. Instantiate and add jobs using add_job(func, args_list) ''' class Job: #IGNORE:R0903 ''' Class encapsulating a job to be handled by ThreadPool workers ''' def __init__(self, function, args, return_callback=None): self.callable = function self.arguments = args self.return_callback = return_callback def execute(self): ''' Called to execute the function ''' try: return_value = self.callable(*self.arguments) #IGNORE:W0142 except Exception as excep: #IGNORE:W0703 logger = logging.getLogger("threadpool.worker") logger.warning("A job in the ThreadPool raised an exception: ", excep) #else do nothing cause we don't know what to do... return try: if (self.return_callback != None): self.return_callback(return_value) except Exception as _: #IGNORE:W0703 everything could go wrong... logger = logging.getLogger('threadpool') logger.warning('Error while delivering return value to callback function') class Worker(Thread): ''' A worker thread handling jobs in the thread pool job queue ''' def __init__(self, pool): Thread.__init__(self) if (not isinstance(pool, ThreadPool)): raise TypeError("pool is not a ThreadPool instance") self.pool = pool self.alive = True self.start() def run(self): ''' The workers main-loop getting jobs from queue and executing them ''' while self.alive: #print self.pool.__active_worker_count, self.pool.__worker_count job = self.pool.get_job() if (job != None): self.pool.worker_active() job.execute() self.pool.worker_inactive() else: self.alive = False self.pool.punch_out() def __init__(self, max_workers = 5, kill_workers_after = 3): if (not isinstance(max_workers, int)): raise TypeError("max_workers is not an int") if (max_workers < 1): raise ValueError('max_workers must be >= 1') if (not isinstance(kill_workers_after, int)): raise TypeError("kill_workers_after is not an int") self.__max_workers = max_workers self.__kill_workers_after = kill_workers_after # This Queue is assumed Thread Safe self.__jobs = Queue() self.__worker_count_lock = RLock() self.__worker_count = 0 self.__active_worker_count = 0 self.__shutting_down = False logger = logging.getLogger('threadpool') logger.info('started') def shutdown(self, wait_for_workers_period = 1, clean_shutdown_reties = 5): if (not isinstance(clean_shutdown_reties, int)): raise TypeError("clean_shutdown_reties is not an int") if (not clean_shutdown_reties >= 0): raise ValueError('clean_shutdown_reties must be >= 0') if (not isinstance(wait_for_workers_period, int)): raise TypeError("wait_for_workers_period is not an int") if (not wait_for_workers_period >= 0): raise ValueError('wait_for_workers_period must be >= 0') logger = logging.getLogger("threadpool") logger.info("shutting down") with self.__worker_count_lock: self.__shutting_down = True self.__max_workers = 0 self.__kill_workers_after = 0 retries_left = clean_shutdown_reties while (retries_left > 0): with self.__worker_count_lock: logger.info("waiting for workers to shut down (%i), %i workers left"%(retries_left, self.__worker_count)) if (self.__worker_count > 0): retries_left -= 1 else: retries_left = 0 sleep(wait_for_workers_period) with self.__worker_count_lock: if (self.__worker_count > 0): logger.warning("shutdown stopped waiting. Still %i active workers"%self.__worker_count) clean_shutdown = False else: clean_shutdown = True logger.info("shutdown complete") return clean_shutdown def punch_out(self): ''' Called by worker to update worker count when the worker is shutting down ''' with self.__worker_count_lock: self.__worker_count -= 1 def __new_worker(self): ''' Adding a new worker thread to the thread pool ''' with self.__worker_count_lock: ThreadPool.Worker(self) self.__worker_count += 1 def worker_active(self): with self.__worker_count_lock: self.__active_worker_count = self.__active_worker_count + 1 def worker_inactive(self): with self.__worker_count_lock: self.__active_worker_count = self.__active_worker_count - 1 def add_job(self, function, args = None, return_callback=None): ''' Put new job into queue ''' if (not callable(function)): raise TypeError("function is not a callable") if (not ( args == None or isinstance(args, list))): raise TypeError("args is not a list") if (not (return_callback == None or callable(return_callback))): raise TypeError("return_callback is not a callable") if (args == None): args = [] job = ThreadPool.Job(function, args, return_callback) with self.__worker_count_lock: if (self.__shutting_down): raise AddJobException("ThreadPool is shutting down") try: start_new_worker = False if (self.__worker_count < self.__max_workers): if (self.__active_worker_count == self.__worker_count): start_new_worker = True self.__jobs.put(job) if (start_new_worker): self.__new_worker() except Exception: raise AddJobException("Could not add job") def get_job(self): ''' Retrieve next job from queue workers die (and should) when returning None ''' job = None try: if (self.__kill_workers_after < 0): job = self.__jobs.get(True) elif (self.__kill_workers_after == 0): job = self.__jobs.get(False) else: job = self.__jobs.get(True, self.__kill_workers_after) except Empty: job = None return job Trimage-1.0.6/trimage/ThreadPool/__init__.py000066400000000000000000000000641344171234500207140ustar00rootroot00000000000000from .ThreadPool import ThreadPool, ThreadPoolMixIn Trimage-1.0.6/trimage/__init__.py000066400000000000000000000000001344171234500166410ustar00rootroot00000000000000Trimage-1.0.6/trimage/pixmaps/000077500000000000000000000000001344171234500162235ustar00rootroot00000000000000Trimage-1.0.6/trimage/pixmaps/compressing.gif000066400000000000000000000034711344171234500212500ustar00rootroot00000000000000GIF89aVg7)Hu! NETSCAPE2.0!Created with ajaxload.info! ,w  !DBAH¬aD@ ^AXP@"UQ# B\; 1 o:2$v@ $|,3 _# d53" s5 e!! ,v i@e9DAA/`ph$Ca%@ pHxFuSx# .݄YfL_" p 3BW ]|L \6{|z87[7!! ,x  e9DE"2r,qPj`8@8bH, *0- mFW9LPE3+ (B"  f{*BW_/ @_$~Kr7Ar7!! ,v 4e9!H"* Q/@-4ép4R+-pȧ`P(6᠝U/  *,)(+/]"lO/*Ak K]A~666!! ,l ie9"* -80H=N; TEqe UoK2_WZ݌V1jgWe@tuH//w`?f~#6#!! ,~ ,e9"* ; pR%#0` 'c(J@@/1i4`VBV u}"caNi/ ] ))-Lel  mi} me[+!! ,y Ie9"M6*¨"7E͖@G((L&pqj@Z %@wZ) pl( ԭqu*R&c `))( s_J>_\'Gm7$+!! ,w Ie9*, (*(B5[1 ZIah!GexzJ0e6@V|U4Dm%$͛p \Gx }@+| =+ 1- Ea5l)+!! ,y )䨞'AKڍ,E\(l&;5 5D03a0--ÃpH4V % i p[R"| #  6iZwcw*!! ,y )䨞,K*0 a;׋аY8b`4n ¨Bbbx,( Ƚ  % >  2*i* /:+$v*!! ,u )䨞l[$ Jq[q 3`Q[5:IX!0rAD8 CvHPfiiQAP@pC %D PQ46  iciNj0w )#!! ,y ). q ,G Jr(J8 C*B,&< h W~-`, ,>; 8RN<, <1T] c' qk$ @)#!;Trimage-1.0.6/trimage/pixmaps/list-add.png000066400000000000000000000012441344171234500204330ustar00rootroot00000000000000PNG  IHDR DsBITOAPLTE+j3f-Z-i @j5X3U8h7g6g;j6f5f:h4e8hhiijkkllmnoopqrss|׀؂؄و؈ډ؉؉ًڌڌََڏڐڐڑڑےۓܔەܖܗܗݚݜݜߜݜޜߞޞߞޟޟޟߢvtRNS  IDAT8ݓ1NC1D D 5 g\@AGA*(R&♡0&SP eyg#OCM0?,;;8Ip4˶]~.)dtimwol~K,EWxWik>dF XkƬ̱ k;P)@HDDEH G93TD0ܽ| +]E %`rwcO>66IENDB`Trimage-1.0.6/trimage/pixmaps/trimage-icon.png000066400000000000000000000106761344171234500213210ustar00rootroot00000000000000PNG  IHDR@@iqsRGB pHYs5tIME ýobKGD>IDATxݛ{]Uuǿksw&w&Lf& IVdS[`QQ,EB)|P+R|R>'EZ)F" y}Ԁ93w=ܳ~(‡zZhZj/3%p9yXuEk}Ƚ*xYuYuU<+>kGA~1cbUu}"+NRRjZ{}^p:\hHk}iDYuKOR `Ub,e/kZM m.Z y؛vGHݧh_.RVeXc΢>$0^0(ޗGk*/!pD}^ry}S@!tN~T"=(o+ Z>!Q@+Pa|jPk-{ ZqJ:W*Cq ~N~"*PZ 4bhVYX ǂ|XQ(6Tx ` "ƪz 4wZ#hu]_jOĒ90ds1e@rb%ccwߪ@+X uoR~;$^|hO<Z> * Q%9 U5a!.õֵ12Y҃&@[,w: kЮBͱ:p Z| WIoE*}`Eq鮋4 )XllV0.Մz8O*3^Ahn$N)C{"r. WU\MŻj76jzĚEFV޺1+u< Fط % 6u̽(hϲks2\ 8Zd3­tِQ>D$ 50m @ꎙ R> 30և?}JZ7O3Q3vX%Y}o1f f201SSeȼEzg,Xb&_FSXϽ"7sjL M4b& L05SfTj?۹O`r)^#(%H^kj8dʲyq.>b߃0" B_t<+hjxӔW{TA+[Dw8 ݷ`K &36#W30dg2 m _aޫ G_~ElOO0 JX2kT+5%! }PkZEs`u$71camЭ ܔ<J9p.l^02PƸ@a(2c@VQod`O# 4:`DWu5۪|}煓W Hh.^Z|6xX"IRƮ LzF4jnF_]fBNJn& p lzGֹQ&`ςT"b:͚!L.gV5b9Chœa3[Ö/W4Ԇ0& Lf$pQP)\aAPR"PJY#an+b:g\7P%SbueC&wP\ SFzp2u)]2Knzc8`~_jDQ\8f^.Q`bng/D=P# ʎrC$hE{ǂ91Vw!0T"A( bvU1>QY]!;u"onl{V)湝`xFzQ$,r?i$ lq^X7(lWhdB\z s ã;g)sX⣽IgWR'FsBa)?QtLЃŰ02hTXwSs `j.wK28$A?9!C,3Z5aQ4b"nvL h:IezS`|o 0Q)XC:õbɮ_<5E%}[0P eOD18&eƫLe|g* m_HFUja #LQ^( FGOg[NCI37-*z{,ed1͉9͋K_19XaZĆR`gʅSQ/D.D*S Q{DžxM$7:*s$1IpFncWdPIW\C‹.ߌ_/"jU0VRÜʅX w)$I. F~~i?4Pi#D Tܼ2Cc|G_ss-E"A4=eлÜ)b=V@7fm%~o|*[.mS,͚Χ (Xmys@mG4*R=2vw@4bop dI|rhYJ+:RĂӆ'.݃xP+Wb[-kfdwc7Uuv=x8I׹f u|2ɯ&1I7r0rGŞQ R.$oř^rh2w $9x>py{۾L3O6Le{(_F%B+f, ~C]QAϚAA4{"5$I>>!zx o6?LOMe y:ZyQU4"4 %;$ȥFKjQi'I^xtbw?O=l6?RځG6:UЪMUbNU+t]Z cfy:$}I"lj_|oko'|c<=%ͼuGȂKrW/{)L IlN`ƀ_򫟚;/^7|˸Gw)ө՟w45z{9/:[ua/QnG= CZIO#e$$IX7=5ymȥ;SO:/<[oI2c3nT]0]āRTŀ;$9Ӂx=߼FṼs_e1l{q)һ:|peCi=IxԽr#v+8gɏ\ws+o`*5{t֕g=6m@fsoK/&g7m|:,~u8?la*&ngwo#w$yg$$KgzM?vM|sg~],+]oG[ =Y`<$$9-I]O5񾻲n_g3NWO7̻O{p?$k $:I$Y4び3w>lv?#p'IENDB`Trimage-1.0.6/trimage/pixmaps/view-refresh.png000066400000000000000000000037151344171234500213450ustar00rootroot00000000000000PNG  IHDR szzsBIT|dIDATXWYl2g Nyꕎg=w`3qZD2WPe3e`]+?p]-39ylU*ڦ<ǝgD`]ӑ:]B +EЯA8R4l' &K7FR80-]ǁƾx'[6i kjWW~:ŕD Ip e֏p(!q}>uvmu$c ~>E'lMK`]s6~[2H q$ %A )KDW9\IMT{L5!?n v2 (w3~шoV (B̯!AخH$ؒ`.guZ[SDepn3~Ԧpk[Ӝ10"Ix~\t~{T"mH-$Iu;e 2Wt-|};Gww#9=V G.`+jyR WIŜI,[,݆=/J9څEDŽ~_GO.Jp㯬\YY;OMmHIg0~ij{ya Ne ɠ‚ VG y7p1v j\;QPP]QXs`fB2 ˁ|87ܾV>Tn)ؤ"` ņ`v8wO8O)f{AWi ӊ $qd*8&ә.[Lt]{r:֭]ͮ GCu`*8CѺ'^]tPís&~r%0g;uAA*tǎQ}v׼YʮJH-?-72x[[gF~Un}Ka|̈p7.qVI#4ޞr\g;?-.sju[ǬU0Yinc^ K{Uնf5!PPeqsr:X*):cs6' ?y6&85~%x7)i?*3ZOYkZ,+c%P3^ D3]BtBtXJ.S/}<|o/79n;ZOF{7T&<>'ϗԁ7#c'3g?p܄+%tU W!%o bi]Eg\n8u7*ϋ[}w<"ƭ= self.oldfilesize: copy(backupfullpath, self.fullpath) self.newfilesize = self.oldfilesize # removes the backup file remove(backupfullpath) else: self.failed = True self.compressing = False self.retcode = retcode return self class Worker(QThread): update_ui_signal = pyqtSignal() def __init__(self, parent=None): QThread.__init__(self, parent) self.toDisplay = Queue() self.threadpool = ThreadPool(max_workers=cpu_count()) def __del__(self): self.threadpool.shutdown() def compress_file(self, images, showapp, verbose, imagelist): """Start the worker thread.""" for image in images: #FIXME:http://code.google.com/p/pythonthreadpool/issues/detail?id=5 time.sleep(0.05) self.threadpool.add_job(image.compress, None, return_callback=self.toDisplay.put) self.showapp = showapp self.verbose = verbose self.imagelist = imagelist self.start() def run(self): """Compress the given file, get data from it and call update_table.""" tp = self.threadpool while self.showapp or not (tp._ThreadPool__active_worker_count == 0 and tp._ThreadPool__jobs.empty()): image = self.toDisplay.get() self.update_ui_signal.emit() if not self.showapp and self.verbose: # we work via the commandline if image.retcode == 0: ir = ImageRow(image) print("File: " + ir['fullpath'] + ", Old Size: " + ir['oldfilesizestr'] + ", New Size: " + ir['newfilesizestr'] + ", Ratio: " + ir['ratiostr']) else: print("[error] {} could not be compressed".format(image.fullpath), file=sys.stderr) class Systray(QWidget): def __init__(self, parent): QWidget.__init__(self) self.parent = parent self.createActions() self.createTrayIcon() self.trayIcon.show() def createActions(self): self.quitAction = QAction(self.tr("&Quit"), self) self.quitAction.triggered.connect(self.parent.close) self.addFiles = QAction(self.tr("&Add and compress"), self) icon = QIcon() icon.addPixmap(QPixmap(self.parent.ui.get_image(("pixmaps/list-add.png"))), QIcon.Normal, QIcon.Off) self.addFiles.setIcon(icon) self.addFiles.triggered.connect(self.parent.file_dialog) self.recompress = QAction(self.tr("&Recompress"), self) icon2 = QIcon() icon2.addPixmap(QPixmap(self.parent.ui.get_image(("pixmaps/view-refresh.png"))), QIcon.Normal, QIcon.Off) self.recompress.setIcon(icon2) self.recompress.setDisabled(True) self.addFiles.triggered.connect(self.parent.recompress_files) self.hideMain = QAction(self.tr("&Hide window"), self) self.hideMain.triggered.connect(self.parent.hide_main_window) def createTrayIcon(self): self.trayIconMenu = QMenu(self) self.trayIconMenu.addAction(self.addFiles) self.trayIconMenu.addAction(self.recompress) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.hideMain) self.trayIconMenu.addSeparator() self.trayIconMenu.addAction(self.quitAction) if QSystemTrayIcon.isSystemTrayAvailable(): self.trayIcon = QSystemTrayIcon(self) self.trayIcon.setContextMenu(self.trayIconMenu) self.trayIcon.setToolTip("Trimage image compressor") self.trayIcon.setIcon(QIcon(self.parent.ui.get_image("pixmaps/trimage-icon.png"))) if __name__ == "__main__": app = QApplication(sys.argv) myapp = StartQt() if myapp.showapp: myapp.show() sys.exit(app.exec_()) Trimage-1.0.6/trimage/ui.py000066400000000000000000000152111344171234500155310ustar00rootroot00000000000000#!/usr/bin/env python3 from os import path from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * class TrimageTableView(QTableView): drop_event_signal = pyqtSignal(list) """Init the table drop event.""" def __init__(self, parent=None): super(TrimageTableView, self).__init__(parent) self.setAcceptDrops(True) def dragEnterEvent(self, event): if event.mimeData().hasUrls: event.accept() else: event.ignore() def dragMoveEvent(self, event): event.accept() def dropEvent(self, event): event.accept() filelist = [] for url in event.mimeData().urls(): filelist.append(url.toLocalFile()) self.drop_event_signal.emit(filelist) class Ui_trimage(): def get_image(self, image): """Get the correct link to the images used in the UI.""" imagelink = path.join(path.dirname(path.dirname(path.realpath(__file__))), "trimage/" + image) return imagelink def setupUi(self, trimage): """Setup the entire UI.""" trimage.setObjectName("trimage") trimage.resize(600, 170) trimageIcon = QIcon(self.get_image("pixmaps/trimage-icon.png")) trimage.setWindowIcon(trimageIcon) self.centralwidget = QWidget(trimage) self.centralwidget.setObjectName("centralwidget") self.gridLayout_2 = QGridLayout(self.centralwidget) self.gridLayout_2.setContentsMargins(0, 0, 0, 0) self.gridLayout_2.setSpacing(0) self.gridLayout_2.setObjectName("gridLayout_2") self.widget = QWidget(self.centralwidget) self.widget.setEnabled(True) sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(1) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth( self.widget.sizePolicy().hasHeightForWidth()) self.widget.setSizePolicy(sizePolicy) self.widget.setObjectName("widget") self.verticalLayout = QVBoxLayout(self.widget) self.verticalLayout.setSpacing(0) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") self.frame = QFrame(self.widget) self.frame.setObjectName("frame") self.verticalLayout_2 = QVBoxLayout(self.frame) self.verticalLayout_2.setSpacing(0) self.verticalLayout_2.setContentsMargins(0, 0, 0, 0) self.verticalLayout_2.setObjectName("verticalLayout_2") self.horizontalLayout = QHBoxLayout() self.horizontalLayout.setSpacing(0) self.horizontalLayout.setContentsMargins(10, 10, 10, 10) self.horizontalLayout.setObjectName("horizontalLayout") self.addfiles = QPushButton(self.frame) font = QFont() font.setPointSize(9) self.addfiles.setFont(font) self.addfiles.setCursor(Qt.PointingHandCursor) icon = QIcon() icon.addPixmap(QPixmap(self.get_image("pixmaps/list-add.png")), QIcon.Normal, QIcon.Off) self.addfiles.setIcon(icon) self.addfiles.setObjectName("addfiles") self.addfiles.setAcceptDrops(True) self.horizontalLayout.addWidget(self.addfiles) self.label = QLabel(self.frame) font = QFont() font.setPointSize(8) self.label.setFont(font) self.label.setFrameShadow(QFrame.Plain) self.label.setContentsMargins(1, 1, 1, 1) self.label.setIndent(10) self.label.setObjectName("label") self.horizontalLayout.addWidget(self.label) spacerItem = QSpacerItem(498, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem) self.recompress = QPushButton(self.frame) font = QFont() font.setPointSize(9) self.recompress.setFont(font) self.recompress.setCursor(Qt.PointingHandCursor) icon1 = QIcon() icon1.addPixmap(QPixmap(self.get_image("pixmaps/view-refresh.png")), QIcon.Normal, QIcon.Off) self.recompress.setIcon(icon1) self.recompress.setCheckable(False) self.recompress.setObjectName("recompress") self.horizontalLayout.addWidget(self.recompress) self.verticalLayout_2.addLayout(self.horizontalLayout) self.processedfiles = TrimageTableView(self.frame) self.processedfiles.setEnabled(True) self.processedfiles.setFrameShape(QFrame.NoFrame) self.processedfiles.setFrameShadow(QFrame.Plain) self.processedfiles.setLineWidth(0) self.processedfiles.setMidLineWidth(0) self.processedfiles.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.processedfiles.setTabKeyNavigation(True) self.processedfiles.setAlternatingRowColors(True) self.processedfiles.setTextElideMode(Qt.ElideRight) self.processedfiles.setShowGrid(True) self.processedfiles.setGridStyle(Qt.NoPen) self.processedfiles.setSortingEnabled(False) self.processedfiles.setObjectName("processedfiles") self.processedfiles.resizeColumnsToContents() self.processedfiles.setSelectionMode(QAbstractItemView.NoSelection) self.verticalLayout_2.addWidget(self.processedfiles) self.verticalLayout.addWidget(self.frame) self.gridLayout_2.addWidget(self.widget, 0, 0, 1, 1) trimage.setCentralWidget(self.centralwidget) self.retranslateUi(trimage) QMetaObject.connectSlotsByName(trimage) def retranslateUi(self, trimage): """Fill in the texts for all UI elements.""" trimage.setWindowTitle(QApplication.translate("trimage", "Trimage image compressor", None)) self.addfiles.setToolTip(QApplication.translate("trimage", "Add file to the compression list", None)) self.addfiles.setText(QApplication.translate("trimage", "&Add and compress", None)) self.addfiles.setShortcut(QApplication.translate("trimage", "Alt+A", None)) self.label.setText(QApplication.translate("trimage", "Drag and drop images onto the table", None)) self.recompress.setToolTip(QApplication.translate("trimage", "Recompress all images", None)) self.recompress.setText(QApplication.translate("trimage", "&Recompress", None)) self.recompress.setShortcut(QApplication.translate("trimage", "Alt+R", None)) self.processedfiles.setToolTip(QApplication.translate("trimage", "Drag files in here", None)) self.processedfiles.setWhatsThis(QApplication.translate("trimage", "Drag files in here", None)) Trimage-1.0.6/website/000077500000000000000000000000001344171234500145545ustar00rootroot00000000000000Trimage-1.0.6/website/arch.png000066400000000000000000000020461344171234500162010ustar00rootroot00000000000000PNG  IHDRE/PLTEDUEWFWFXQe >K JZ KZ KZ K[ L\y  "0<2=" ;I @N?O(3>MFXDVDVFVGW IX IYBS BP'1J] M\@PVkUk"loqt~au}z|}} HWzNaOcRgUjf  '`tRNS"27>D``fhopppppqyz|}IDAT(c`@\\ 8$#Ix \&OvU@#n8Z%'͜9 Vn3f* ĮUHD̸kvRJngaʯ`n85VÏSM왍E+?Џ3f͝:mPk:I334`3fwuF#kkkoj-F;$|- pPƆh\+-/**/2JlZ*@rťj0y%@^^)NP* *YIޖ.Ls,A\qw=Y!$9@\?#9 f$OV@[T=d sUm bօ ~I4AXcI(DR,"Ȁ3! M!&O.4/IENDB`Trimage-1.0.6/website/debian.png000066400000000000000000000005441344171234500165070ustar00rootroot00000000000000PNG  IHDR2E3PLTE6@hCꟳ` O揧0\pPuK_IDATm r0y_ ?m ŽR7 !^CH)~Z^kKqhyf>j>O88888888888888888888888888888888888888:"/lyaa "OY'¶?0 0 <;V%^&C|xI:I侎{{iheaa_kxxxYo3qLU,v#[cay."jE5[ ۏ=Q^2}[g])8=gkn9aag&4IMXrter$o?U(ۖ-p6q؝8y % 0 0τ 뻠nSOk''%qrygpHݍ(}uPfڢ (ޓR'[G92T@iR6^4=r,&g025rOM܁_ޟnYj1>I"~]1EXNQRWY$Jk$WҜ:R?f%V0$~AN&B [t8EF]T ](u_KՋH{Gh^\6 cDĭuةEigJy[h2 v}_j0tڽDp"lm4眂wNhYuMYyv70TlrK{gBXH'+xrm"?چc^7P wVO2#s N&[JrAT$i56jplZ+ӷ -VGIL:l܌ީnUy7_ 5 0QEvM`B_m'?M]/˱\ץ>>JFT!#"(%I(׸)q{:x瘒6[__?w~L*`V܄7IxơZҥoywA}pAHq+Hv-!zՀB ZT `woE>TAT {oI3/U^7qN陃#d_ z^sU|Z4lِex]S1,cv2+mS$ vTKW3w1<ڌO<5pLvYgcPxk,zR.8fnF)Y$,ʹ^ R4֢޵LsCP)rp<7 -4ll__ j@,j+TT9$ z~OX/ $Q8G%5cJܞ5:jfX~Ow_&~g5q ܣR܉v_G!Ā9$ޞG mq!j<|4>j0}3q;˕p#~(pI$9hac=5أ*kHS\^7ᨇ[Sk=%IP]a I#p"Iک\W]a&Af<8zRĭ ^xj ŰwAhY~H5vѝ邛"یj$RPA^^hd0ĸ"ʯ%z-*\suVZGBʷX#g%!EI;pZ݊=ng}ɻpNE|,j瑣_>ʻvp ˨\F=^ c>v#$)PTѧ-!x RMT(|I2s-C걏DCå3-FEl]èn@YL]# ,$08 TV atPep޸fԍ/`bjsh(ǁ$o뉸 /Z/ OadtC p0(=YXpg+Tt } ^-[h")̭C ‡/KM}5؟ЍΩyNR/3y&CfQt]~-1K|7FHCqum_  Yq ^{y2"t/z}4 >BFP?D&ʛ n'\jx31uܵiǚlw˃xN-f ߕxHiħ杇' t:6Ïrӛ|2 |H@ '4jWG x ;<ҰYFǀCPX_` D|.QN3G;h.TQ:4Bl.[0na!7(nyxE&Y.ŷ!2ӯol>Ul긅kn. 8ruZ{M!mguorh'7g \vpyEjI B0fC L=Q-{26vȰ!Y[;[H$<6  95[xn&n'.(?D^HBz4 _uK |܅G`l<3a#`55]ܓ9ҫ(3wfmҀԝ 9kܞDXN1E8Y`<|^F@T2\yaagBرR1fB'n 0 0gC~ aa]{i8- c:h$[4&34qɒMK0˶7ܼ`% n  e"Bi\ҖR ^YOɺ؄mZCyQο='m!"/kF&g,EZI3,V:vvR*E̟-8v3#ir5-`y W !JR;pB[/ٗ>AfgSF}8^t9MIUH(QaoF3Үj-ok9BD੧sbc{͢9OtliR\퐤6ÌsLU/.FX_h4lwnMg̹ߗ}MES/A,yu BBao_€\XxX)]%x/mdw <ЈcN? "0ivLt<>sGRQ+Jj{qV!6s.q?nBרn+cMDˈuj<*=Nˍ!Lu7aXU0ݞۃF>q$Sw>9G;=w S0>.RjU~XTpS( dN''}x);JDȺ`@O<\;Z#Jb0'<0ESU Q僷s oS1(mu$LÙ0w!*1olezR/n.uۢ(S̙SLaޙHI#wܔv?{1, `;cnO>5FրR~yeAZpUSÓ1`$ף:hГx afs&W69P+O8`7uWcSf$4zќxiu]GBpysDvNSu2EΙ6*y|SedwBF CY {0,"' ᦈ/Ƴl_r2 9V|۠,vI&Ũ} c8Sm&_wbЖ.XU,f]ik pL7eX}SEc_&XP6oWYk9T5#VU5+Nyc\ӠCG+qOQA؏LYI`ϴȊ{s=>\mw0OB5 WnS ߂)LW?潻)7 739?y}% YRRЅ30՛dP$I_yetR٘Ʊ+D FѡRcM{-VZ$/_ٚdb4bg3{vIғslN-i\uodah"ljtmߏy?gah zit͟q(à73xkMS?] SQF[zjOwܔzY8х-Y6quh8E$nzj o?%ܦ,;ŏ=dɮpJJmwkjVa_iD)Y_!n8Zps67n6(dY3Z6Ҳ'|3YyDk3Xv0%YC2$4 z/ȯʍn+H$x}͵ʺ6ft8n4R__Ӟi]=,]Ì0G2ٝeҗpdnnHTsWu\]AQ%Ӌ;XkT%rYGByRM⢱ޝ%e6.( ͘\*!b<& rNTIsr\}}p. U(n8?e՛9k<=l^>xBaߓe}VҬ}x٫7U?uFKA+rRnoߖIl;ϫڣڬz|!Th鼩XE)_{0,o3YΦ#~&cQEdÅ ~~b,EPm=f) ^xs|rm9 [nQ]mi6!q95ŒhhUf%<)Բgdg%f$x۽ŒJakxq3|7%;hLm#<sGg ޾?{x)L0Y$+K4>Fv=4;2* 1[mOGImbUj# \zWa0Ʃ3O<֕6clgL3K ד Ă,bBqlם~p"ָ­C<{{;z3d\6򉻲(NЧ'3u^˚K<Gx1<˹۸~nvd&OK3¥gM& b..N8_ӍocX88 c}n͟['1u.aȔ$M\?5_{xדqmkǮ\B`Nݏ-oG}=:uYGBpyg$=]Ky/#(O `w2{8IXRó-5Gx{K㖞el=4epFi뿶WԚM,fs!6[`a,DWI:C3d8^3Ƃk.ڞqBZkZ "V h-7_Yq3 z>vV˂ɼ+$W[ +`y|~ք3ais"'Vݼy?Deee `0i3^6b/\+*e[Ee$nɀr%2! 5o**]q "?9.TEgΎ8/?96^a+69׊Zv Y\5E& tbN\Lf,ړ"a)rlZ\HiFP$Y[M@ h=4?o*9h40~b݁P#0k1m;cxwgoImXfmMW>̔Oϰ $^@'#yS䤆i­ʊ?DYрDO\#lHLub[Q^&mpR ]q@t\" DFrで|]sxyY^Xq?;ǔAi>X~X]M#7'['6G#Xc#o$y ˶nY'ܒRᕐ7.^ !8,'pO>FCq ;}8rp%*Ą 6G#WִQoA7@кh~T8*+#IIAh%.\Pʗ/]JrJ{RL+n5ղZ!AyS[rznڬLү]%5%Yr $&rZjRfQ3J|h!Zp7@&Z-7MF,!$;r2@S%&c-Ln6o7ADg۷lM <()rRnJiiZi:m/D9IIGFn?R':|W J_{Ib|T.n@@ IrNj5My1Ϩ[^~%J$ym/D9I^ l1 =G~+w r@ xySpk'@ AM @7!@ p@ &;,3mw[GL #!OQnskk_mm[>nZUQU "ϐI*i7w vaܙww~s9k& 7nAA$  nAA$HAAp#  TE]tE]tхR(/w`Yjvwp{  h+JJnaPVZxڊ֖n~MMhjlDzmܾ.n-AAG[~*d_~ur>ݽ+6+&,cѠA3.n-  mՊeooZ[[n*e˄ z4W}(V];%4j˄ z4BCu8P>Gv L,޽A=}(pZmn՜,9yWs ,6W>A$xAMg;X=vwM 왟xzr{`n;9IG#Ёf[f#V _NO~%  E`֠|z+zn]s ~PR)d0?f7k)v=^ƙS'pSqixˆA>vp7lPEG6EWC |s:nX=v U؟"tV\3Z9LL`*XP+#Sw]㱽Ȅftu*WmkKx~"1VemIѯ}tnχxB:q#5|綯@fm?nlm:%Ӷ{Xdwpknj$ e^->/ RYMuP "(h⃣e v4LIءUl45A#`p8=QA=yc6"A{ v'AeC{=OPdn}(5 rFa  ]{LLȤ3k30:26z!+ s 1٘9h(LbQQv }ҧx75Qau8 'FaYn {(o,SZX+3'cҤ1&<sv缄cbʺ[8U!+^ _@"xZkwwgk)RV ?N0P'ܸ wp =^ĐH$x)eQMS0G*-^Ki*}hM=\!5rJ!gr ,TZ3mY@+`9?aΛ07Cu4 {❴j7>ZOyt#ڭg֞CVlo}1HSJ퍈ǶbGs8?s%À3eُ5hՈF{*`֥q5=Z3fBa,,EK1,\gM4 ~I[LJð亹q>³Rx?Xl,dmBq,B!۩~-틤omP$y 1 !J4:6c4ӣPYF-qv_W$܈.n2_,,`]Cj3GXy6#b0'A~qX^Ă/DhT >H ^+cLx"c5EuPܒ(~4Wc5dy?`rf7dJ- Χ՜A4*6cG10d;n3QD$Ja:$(އ xÙ*#Ouvda;QʴgTEl\sh/PضSvE<1mFE߅7Vb9Zs~Dxis~9Za_f#R6 tz!+ (–P !b?k %$a+BJe6&`ۆhȓOCev8}bFb,âp4Palt Bo;9r XgcŪoX(x_EZс/tJ pMyoHĉ|2mX-ngeRύӸBƩjGMMjΎ4l;J)їEk3j94l`zG7Bh{Ƌ 5Ff*kR$"$h_70("bT5lxý>} hۜ$ U>o}aօCWp/䏾X}B,( X+2)qen,!n&΍0?!I^ =o3iH)+ .\ L&-!tgEyslmp3 n\TpRViǬ#% 2`I^nO_8Y%PH~'!%9;PbhO_>pQQ{ :G7Bh~.ƶ7GO<1xk[Lu֢` L䢑HjW؂٣!S0L,>UȞAHx8mDyY*!+@,/Ѐ3UȘ)oE' Xgz#pXֈC@Wa1cDŐȆ"(?7; rEL+*lgV$ ,Rpvv~xʀʴ1~CFqv gkqk z4BCu8P>D#ҕh/f,9]3X 28|#Þ.;Ϊu1 z4BCu8P>[KѡpۗeĞKRb 6SJKƚXm)zf4 z4BCu8P>GA ^LA, zAع^ztxKes݄, $lH%Fj%2 xiLՖ`khE@*PQ$B.K6{vw=@LrFZA3{f;dy=([EQEQX|q;qEQEQ%>_֮Z((狛8TDDDDTTDDDDTDDDDTTDDDD{GMDDDD;n*n""""*n"""":*"TADD>vkye z:U>f{,~ ~b#uͱ%{z&yɟ lRS/2u )lkc0$k}l5G/{q5\3e0iM-&`wL-Tu?/zmv>qՠsKAw_K(IVFLg`A9?.XTD3c7I,>؎M% Lfm̵(X],5RFC:x YDzI& ̚eN4XQL^*cZGp=ҾSōSrc ա|#qS޾}Sϟ%eн3̌*^8FzggP6)7Lfgy<øY1%y:<ۘuc FXXv I+W~fY;EϚˁv¢61?t'-w{y؟J?[seٷKH5&hz ֝j=z=n$綝# <=9»ߤQigNqsK/ʛSܾ9dL->Մpj&gUyY%}ajVN ~Kq8M D͔g<ؠDfokg 5TDl ]KRzX3:CqK!qqzd/ƒ00m$\=ҕ #.Zu;8xy<IH!GopA^oyDs23daq;[z8~Xڮya׭}evߜ;&u~D-M Fů7Ӵ uZa5l եel/pB7uۚ&r%r7E;i"FFmB/I̪`SBGY;KPWe~0uplBfǴ9PcBZ^pBᓼ$@BT%e)Z?͜RZZb]Sܜ tXΛX(nl<}~F>>)n]G׺B4ZPq"i?PAo6BYˠ@XzxwrmQHTr2t}&=pNAF%KKﹻJF%BGgRY%u,?tydAB yV\-GCX>YH1m>/\Ӧ f O)"'V23Uܾx%IP GWJ?w,3Ry$]n^ǭ]CDʻ)򥒞%5{* ٮcES=N1yIII!)udR 0kX-X|DgI cuT9 kyfx:d({X{LNɔeG B{`LaQ̼ͧ6f ?#ssF>VShG$s;ƭ}*"""&"""&"""&"""e_vƫIzZ_7`*AMejqttV]펝GejTɰjuVA_DBHHBM8GEE8ܪP9{o~߽bX,FqcX,ŢX,bQ(n,bXIWp|8pއzڍ%;BwN։Ϗ7 ! a?*EB e۾9\uύō?g<󠸽!Bؘ`F!lL̃0ōDa7Bac"̃yP(n< qs9֌.Scz{0og?d߆-(D WA&S#=eXMJ:蹈K`wˢaHV@lḍ[ު=JAIY|7>+'B9l>'K*Ϙ wʧlx:)n7By$iTX``H'Wz4>EAr ۬Ap${IH)j;jVdܹ04˕q E7{̆d>P4 SAX3ߟ,OI\?c1&yڶb0?k>8|Z6UOִ5!NΆa)D׉H]$Nrq딥 ݻwqܾ}tvQW6t&_k0U;aGu HrǬ_+l[`I@6Z}Wv[m=yg8,EDaՙqeXpR(R\h 7B($aZ-41H\/B` 26-=,zFAj D[rQg̣)}Z}6*3Dvb<D~юXĩ~ Wg"Òyyk̋-qn b,gt "ommm'[{C?x2woq1!GҎ;x !8Jbb*x쬮))z|+RZNׯB78vKG^FȀ x~;aYx괿/P QC^.w 9 \^.Ă@"N(S*E"Z  oB[I$ =!B½$ۻOegGls9B4yfogfϳYɇCMMBBBB.L ɇ\$$ 7 0I> qMAA@msV ]>q r>AMZ  "nr,TAsEix<B#  6MuChmia``Y 6ĭ(!o.AEAAAm숛!jG%妵χir,TAq+Gee%mmFzNuttϻo$'U^. ! m|zVfs}1j)yQ@g}VepRp=ml6ldD@!4MZ('"J3 'c#.6yua@Jq(f+8EܾOqucuٳ3j[k{!q>ꦏytoۅ}A!Wܴ~ZEaOJ(hރtZ\O(D 5{,ۺAq0XYyeO8ituyQu@Ҷԧ99ӷ^-=΀j4 vǝ<}؇.qB8)2QKu3q40|jVQ9e.U4)O^4@=w2'Q? a ܳNc^Q1>gيSmŭ Bɷc8zK։&3I(@{ax$cr e.&n3l)Br܍\{k;c ;;3lXqC6} 'Yجs ƻ`4*n*<9h/N+{ύB4zw:IE.'+!؎&:N?G m<)Nvef'ߘWq4{9G \VQZ(meTUL"yw 2[#u@(n|$B/b.{ES\\ƺ~t% g"/1dfHWNZ)5Wg?%vqqޏsX#ۮV1 q33$%8BŶn $7ɘv |w7S=Լ$;Ո33aH>4*k&bX$NktJv^\)a5*ٌk4sgzKef8qs jbi ~qFEkWJu`Haz>y؋Js&WxOZg6SpUZcJC~ac&UJǖy$o.nZݕ ]׸[{M5טWqSkiuuCJ᲋) Ynu(:ą!H>tT܆\43|E&h[D xB֟RAm3xL 8ޠ]q*TT2凉يqpg-yKN[1G0⊛$Ln@}!5`8j“|z9ΨNvJJ(H>\qr- #8&21q}C1gM3 !`ss,MHZc%0L?1C fX+5r6rtTʙIDAA-W(} \FJQ;da PɴәjOŨ5RwA7' ]FKaa|~AA1'n&ݯ.wȂ HM*n sNҮ:e ,2$Z=v4uN{ܼemVjygSd *E. s{/S\N28s 0yD7YBBB*nnn}&G9Q(7 7IHHHHHHHAp3 C$xa?HU%&D!&&$1!&DD!&&D!h¯ׅ"H!?Xpg䞻7Y$Ib?D⇀n:޿|< TZquuRuDʷ\  ē00=kVhnKcBH'˅/l}g<]?ߝϬa,/ui֤[IҪ|fEg@X+1g[G[{IDGX<=蔗xPj=d,,H5@w:3IPet=ݺu7oLJkK --4S_WG] hnnNBsJʹb[0m'y VU=89D>Z.>wy,joPМGYD3b^j*G3dŏꇇ2'}mK'&gC\a ۊk-9؇xf%+=#Ni¸  9%#PVZ ]jN21̻Ju>N'־"BM(^Hsq< SM;{Xt 90׵"w|>@ZqѣY_fV<8<*+أDzЋn)6+)f4Cq6DLQ /P/RIZpO7S74=5l:mSXnk{lLqOp  Q2h]uv"5-ˣHO\~l|8c<5C{n1yHfÖOˊ}TdNa{)ʫ|aGp3t7Q1XDE3˚ hH?ń87N^(g8BT8euV<2(C98f.죰Qx.e0(׺: i=}-:=;?bX+㿮fTּ}ӏyg|ij!ۡjfR'Ws'E39ǀw=&LJ.QʇnӧMbମ6jY brFFam2i2[!-tt+Z']\5[ #WM!3!׷LbgPoL50J͎`6pܝ7Wss~ߗT;mm`k]'cT=.cu[7VHSՄ}-%Ac2GCsrR jƑS蜓YG`JD kc.tr/1nt[KUI+H$~o9.$w|^OomN-EuxiZ )))$'0+~"/$? # vV hk"HH?VXNccOJ5yI"HH?DkV0 iC&%F !,FPM]IJAą!+TPz-{Mų^n.Λo_S}q=Gπp[~6o߼nW/o ߿ynxn3>}l0]sqrߵ ~{ҳ qn[nZ'N`3ngLTR-2Lʩ@gr֚&nm- d,ɉ.9XN\l&CJGrc/\ udIC֖BHC1j7׾/CȮ@Je,/}(jn-͒Pe.RF2@#*n,n-Ies)dOzRzCDQI)=7W!dB@&xA nLRI7Ǜ%Zf;*kٽ?٧r˔IENDB`Trimage-1.0.6/website/index.html000066400000000000000000000156521344171234500165620ustar00rootroot00000000000000 Trimage (lossless) image compressor

Trimage image compressor – 1.0.6

A cross-platform tool for losslessly optimizing PNG and JPG files for web.

Trimage is a cross-platform GUI and command-line interface to optimize image files for websites, using optipng, pngcrush, advpng and jpegoptim, depending on the filetype (currently, PNG and JPG files are supported). It was inspired by imageoptim. All image files are losslessy compressed on the highest available compression levels, and EXIF and other metadata is removed. Trimage gives you various input functions to fit your own workflow: A regular file dialog, dragging and dropping and various command line options.

Trimage in action

a screenshot of Trimage

Download

Debian (sid)

Trimage is available in the official Debian Sid repositories:

  1. sudo apt-get install trimage

Ubuntu

Trimage is available in the official repositories:

Download for Ubuntu

Alternatively:

  1. sudo apt-get install trimage

Arch Linux

Trimage is available from AUR, to install, type:
  1. yaourt -S trimage

Other *nix

  1. Download the source via git or bzr (see repositories)
  2. Make sure you have all the requirements installed (see requirements)
  3. Enter python setup.py install into your console
  4. Launch by executing trimage

Help us make .snaps, .appimages etc: contact us or open a pull request

Repositories

Git: Trimage is primarily developed on GitHub.

Bzr: Trimage is also available on Launchpad.

Trimage is MIT licenced. We encourage contributions via GitHub.

Thanks

The following people helped develop Trimage:

  • Hugo Posnic
  • Neil Wallace
  • Jeroen Goudsmit
  • Tarnay Kálmán
  • Thomas Lété
  • Kyrill Detinov

Requirements

  • python 3
  • python-qt5 5
  • optipng 0.6.2.1
  • pngcrush 1.6.7
  • advancecomp 1.15
  • jpegoptim 1.2.2

Command line options

--version
show program's version number and exit
-h, --help
show this help message and exit
-v, --verbose
Verbose mode (default)
-q, --quiet
Quiet mode
-f FILENAME, --file=FILENAME
compresses image and exit
-d DIRECTORY, --directory=DIRECTORY
compresses images in directory and exit

Help

Please use GitHub for reporting bugs and email help@trimage.org for general questions (though we are not a helpdesk!)

Trimage-1.0.6/website/linux.png000066400000000000000000000022231344171234500164200ustar00rootroot00000000000000PNG  IHDRr ߔZIDATxڵ{L[UmBˣx`*0cl &&D,h0,&0bM!830qy` 5}pͽKoi~9K[LLjZ>`EEE~PBF#.AHHRM %%%xp: 0{ߵ&F&e>ZHqqpuf%b2 }zm q/_E`<4!\P9s_n a,aN 9`0P]] Nw.GZj_'xpzzhooGRR4BngXDFF"!!A~ي/)66dM477K:RRR^MقHX^^GO^/KCɤB9H>j!e||\/7#]O8Tu4{zzS'>>^{ooelPp8齱Z, yxQ|{j\"g6>L8آf#_¨#k7/=[ƾe TrwuWN0q0wQ `u`/3*8>Zathi$S8OH7gK̚[q amL2SށU1 [OF)Ū0@$0SF&qSh;F\p7 28l^+U `(M`(  Xp%444VUk""!͔kF44~qHw礓r]n~Fӥ&'M/z-IEfdCuv& >[XjDg=If{0QL"yaz{򝬒_ 4f['[_{rY&/]& 厒3 dI$r&BKRd |NRѿ8 e w?u&\iyiYIENDB`Trimage-1.0.6/website/trimage-icon.png000066400000000000000000000200111344171234500176320ustar00rootroot00000000000000PNG  IHDRdgvsBIT|d pHYsd_tEXtSoftwarewww.inkscape.org<IDATxy%W}?}7o7},-4&dLJƐKl;ƶRʔc!5.la@!!!4AћyOs97Bho֚sIf>>t~d 1ppp)\q^񹡯U5\~=0 <@#pw׶G~r?t9@ 6 |sU@^*˄;~x=Yҷ^/(TRBdԔr@X^zYXz#H='/`7^ܵ56K m\]E>"b;گkoxѫ$I7*b7c:'f[hOU)W]`/D)8 ^_ Wm}E+!I\ F}jx?-dugDH !V+LJԦ=t_`B1/. 1N޿Dm1 gEȃnm6bR!('j>6JJ\)⋆ԓ$9cRl*%>8E[DJe ,/Gez8wv=:Mf{ީJJ\0ZƯB5O 0gŌ0q3"9!ʶQY(ۆ&ۑk ysH$?0# Vn@zqv7<#5G$תr9$ʶZuu7Gݟ{ C$i^[D Gt. ^V#K"J Q]Xuh|)G`l Fcӏ1|{Qҹ^JO΅(90(ꑘ=ֈ`|b Gl7 I$&NNhT7iu: b+]_`$ }q[?9FRtE&Ub z*MނF柤:{e )װnv$IfS$Irtч !CyqzvКX[Xj3XNL_sK}80_N5Tm^$IUzl[1&]{%ph д΁i #B&> 3il맚C:4"Kifᅩ#oDZn!5N݀h$b%UtR fwD-n9L7l_3  _x7 Z4XR-xD{VWa%lTjRf+5/:SUVJl6:݇Y%M3H$gk=|foBJ?}zِsRЩF80\=TUjCg<.L^j~$P:hcRwDD4*BN^jppSg&f-iAD?% t _6{lnÎ5GƥFMΛÛLT^OHE v ;Tf~5t) (ʹAhBwK|mo=!%p7aP[3YX7q`CzMVrO9_GsdVԪ:nנ3%Âʉ )6Q+Ԍ_)G,\̍FlY20ࢃD|D.Vv/ ОYcWkc?G.lѨ)vIL"ᚹvjrҲizAx+]!>:8&"aѱ*Mua0Mȱ3Xg$=5;JcQP(P윭qd ?%Qb?LSՉ[d_o5b̲?6Osl i+PmUrL[Kwz>B2$|+H`Hؓ0HY>^ǸvC:(2g~W cҨ7!]US#BJT#.4DE{0|F .QE7;JZkL |Qvb;51}M$M͸fc>>O (QH?X{xVY!YߤYHyhrK="-'"\D$HYEaM[NJhՠ=>?PB`~?2JNU!ЇhDTz +W\X3F&qD>A N˼yn/tTS䶯p,bۅn0$I2 [j*Cy%Ժ( Lލ''x[T݈slvROg>9`fU3v\)XQq1E!h7hF0(}Q MeN% 8 uh'Kቻ"U&#<Ф Q>#~|2RYfmUvЬ FP{X ͱJbWt x}&1ߝ3jث(A AL\׊vtXt%+yMt`FTp#Ra~^7F*G` 3ibZОe~ɞ ّ ۮOsx]C`LN3K–AP] C)QvK*rjBE/6%Bѐ;ȄrA[VB(QqgL;k4 [th6L֙&:(w-su8qǑW/IyܫMEP)d' P^oɄٟh.>B}cu8ޫHA^tr^J`|(fa^Y*?Zj&,,7<+aj(kƖk)M#JUeQ#[sO}q#·_tߗ56^e5aڍ"*HIt[f; E0m'N/<20u+J)ʷ)ĀV}4??@oݠòyp li yYZ凍a| #hDº!VyRڵL2DW*Jd A w3v{7}T^\|͞N|w O%햜:/%Bgxb@Y6+$~ly\{X9X~ܾۧZjԀNyi$ 8Xj3ƷTXTU8n=ؗ %/!S?r-[Fhhkn"#axZΓ!hw`$'^UdMaoLEˍJ,q֗L3"w{'~i[?$I!3 yY|cۯhchH("{uR*D?:R!`˫@ T;^F7rT7i_,تf|U;ajvC1?Vc_DTd _H:ިGe4΀zP+cUD.j#o_o?V1-~1^*R^+ٜNhL#RY b7 : U 6uHכJC(R=`Z*)ox%L+ܙ<=W2(vZ,L Ōw"jx`x!Xa6FeJ#N)A zjjb0j-X>z{ȷ5$ ku˽)*3]̦#a1ډe`"WmiB=ʹ%4(UU"~ PUNEQc+%8ʯ?+ң7ue+v Fj)FZ͚5\ @.9ɇvu%\LRvaՌȃGd%Ϛ0 K 7~O kW069öپg[ mTHe\k@_|?#ٸӬ&^afa [vgˮ߲Zᬅ+ijJ'~^_TTs9pv 6ՙ߲;iq?S Ea "rp-?@ABCiDFGnKoMQsRTwxX[]aÄÆhjkćoƋqrǎȐvʔ|~̗ǽӆԇԈԉʿՋؒԨժٗ٘ڗڙ׮ۜݠݡڶߧ޽߽5E)tRNS@fIDAT(c` Lv^KIh5zǏgGk5Q@I$ @rrE#u1% &NrX,7-+z?~BwKpBngw .{^PtdOWu%s%5[ Y\c$''_dWЮ핌>OUU` 4B{YW>MVnN h*D Rerrf[Rd٥@9¼"9J)BҐq*(q5 •nV0gSzEaWєa<,ڀ6`ogS-q/i،<86JKN:k2v ܙ8$0)6 IENDB`