photocollage-1.4.3/0000775000175000017500000000000012716673066014745 5ustar adrienadrien00000000000000photocollage-1.4.3/PKG-INFO0000664000175000017500000000236012716673066016043 0ustar adrienadrien00000000000000Metadata-Version: 1.1 Name: photocollage Version: 1.4.3 Summary: Graphical tool to make photo collage posters Home-page: https://github.com/adrienverge/PhotoCollage Author: Adrien Vergé Author-email: adrienverge@gmail.com License: GPLv2+ Description: PhotoCollage allows you to create photo collage posters. It assembles the input photographs it is given to generate a big poster. Photos are automatically arranged to fill the whole poster, then you can change the final layout, dimensions, border or swap photos in the generated grid. Eventually the final poster image can be saved in any size. Platform: linux Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: X11 Applications :: GTK Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+) Classifier: Operating System :: POSIX Classifier: Programming Language :: Python 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: Topic :: Multimedia :: Graphics Requires: Pillow Requires: pycairo photocollage-1.4.3/setup.py0000664000175000017500000001130212716673056016453 0ustar adrienadrien00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2013 Adrien Vergé # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import distutils import distutils.command.build import distutils.core import os from photocollage import APP_NAME, APP_VERSION class build_i18n(distutils.core.Command): def initialize_options(self): pass def finalize_options(self): pass def run(self): if not distutils.spawn.find_executable("msgfmt"): raise Exception("GNU gettext msgfmt utility not found! " "It is needed to compile po files.") for file in os.listdir("po"): if not file.endswith(".po"): continue lang = file[:-3] po = os.path.join("po", file) dir = os.path.join("build", "mo", lang, "LC_MESSAGES") self.mkpath(dir) mo = os.path.join(dir, "%s.mo" % self.distribution.metadata.name) if distutils.dep_util.newer(po, mo): distutils.log.info("Compile: %s -> %s" % (po, mo)) self.spawn(["msgfmt", "-o", mo, po]) targetpath = os.path.join("share", "locale", lang, "LC_MESSAGES") self.distribution.data_files.append((targetpath, (mo,))) distutils.command.build.build.sub_commands.append(("build_i18n", None)) long_description = ( "PhotoCollage allows you to create photo collage posters. It assembles " "the input photographs it is given to generate a big poster. Photos are " "automatically arranged to fill the whole poster, then you can change the " "final layout, dimensions, border or swap photos in the generated grid. " "Eventually the final poster image can be saved in any size.") distutils.core.setup( name=APP_NAME, version=APP_VERSION, author="Adrien Vergé", author_email="adrienverge@gmail.com", url="https://github.com/adrienverge/PhotoCollage", description="Graphical tool to make photo collage posters", long_description=long_description, license="GPLv2+", platforms=["linux"], classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: X11 Applications :: GTK", "Intended Audience :: End Users/Desktop", "License :: OSI Approved" " :: GNU General Public License v2 or later (GPLv2+)", "Operating System :: POSIX", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Topic :: Multimedia :: Graphics", ], packages=["photocollage"], scripts=["bin/photocollage"], data_files=[ ("share/applications", ["data/photocollage.desktop"]), ("share/appdata", ["data/photocollage.appdata.xml"]), ("share/icons/hicolor/scalable/apps", ["data/icons/hicolor/scalable/apps/photocollage.svg"]), ("share/icons/hicolor/16x16/apps", ["data/icons/hicolor/16x16/apps/photocollage.png"]), ("share/icons/hicolor/22x22/apps", ["data/icons/hicolor/22x22/apps/photocollage.png"]), ("share/icons/hicolor/24x24/apps", ["data/icons/hicolor/24x24/apps/photocollage.png"]), ("share/icons/hicolor/32x32/apps", ["data/icons/hicolor/32x32/apps/photocollage.png"]), ("share/icons/hicolor/48x48/apps", ["data/icons/hicolor/48x48/apps/photocollage.png"]), ("share/icons/hicolor/64x64/apps", ["data/icons/hicolor/64x64/apps/photocollage.png"]), ("share/icons/hicolor/128x128/apps", ["data/icons/hicolor/128x128/apps/photocollage.png"]), ("share/icons/hicolor/256x256/apps", ["data/icons/hicolor/256x256/apps/photocollage.png"]), ], cmdclass={ "build_i18n": build_i18n, }, requires=[ "Pillow", "pycairo", # Also requires PyGI (the Python GObject Introspection bindings), which # is not packaged on pypi. ], ) photocollage-1.4.3/README.rst0000664000175000017500000000466612716673056016447 0ustar adrienadrien00000000000000PhotoCollage ============ .. image:: https://travis-ci.org/adrienverge/PhotoCollage.svg?branch=master :target: https://travis-ci.org/adrienverge/PhotoCollage :alt: CI tests status *Graphical tool to make photo collage posters* PhotoCollage allows you to create photo collage posters. It assembles the input photographs it is given to generate a big poster. Photos are automatically arranged to fill the whole poster, then you can change the final layout, dimensions, border or swap photos in the generated grid. Eventually the final poster image can be saved in any size. The algorithm generates random layouts that place photos while taking advantage of all free space. It tries to fill all space while keeping each photo as large as possible. PhotoCollage does more or less the same as many commercial websites do, but for free and with open-source code. .. image:: screenshots/photocollage-1.4-preview.png :alt: screenshot It provides a library to create photo layouts and posters, and a GTK graphical user interface. PhotoCollage is written in Python (compatible with versions 2 and 3) and requires the Python Imaging Library (PIL). Features: * generate random new layouts until one suits the user * choose border color and width * possible to swap photos in the generated grid * save high-resolution image * works even with a large number of photos (> 100) * integrates into the GNOME environment * available in English, French, German, Czech and Italian Installation ------------ * Fedora: .. code:: bash sudo yum install photocollage * Ubuntu: .. code:: bash sudo add-apt-repository ppa:dhor/myway && sudo apt-get update sudo apt-get install photocollage * Using pip, the Python package manager: .. code:: bash sudo pip3 install photocollage # you may need to use python3-pip instead of pip3 Usage ----- After install a launcher for PhotoCollage will appear in your desktop menu. If it doesn't, just run the command: .. code:: bash photocollage Hacking ------- * If you need to install from source: .. code:: bash # Install dependencies sudo yum install python3-pillow python3-gobject sudo apt-get install python3-pil python3-gi sudo pacman -S python-pillow python-gobject # Install PhotoCollage python3 setup.py sdist pip3 install --user --upgrade dist/photocollage-*.tar.gz * If you wish to contribute, please lint your code and pass tests: .. code:: bash flake8 . nosetests-3.4 photocollage-1.4.3/LICENSE0000664000175000017500000004315212716673056015756 0ustar adrienadrien00000000000000GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {description} Copyright (C) {year} {fullname} This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. {signature of Ty Coon}, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. photocollage-1.4.3/po/0000775000175000017500000000000012716673066015363 5ustar adrienadrien00000000000000photocollage-1.4.3/po/it.po0000664000175000017500000000470312716673056016342 0ustar adrienadrien00000000000000# PhotoCollage translation file # Copyright (C) 2016 Lorenzo Bicci # Copyright (C) 2016 Adrien Vergé # This file is distributed under the same license as the PhotoCollage package. # Adrien Vergé, 2016. # msgid "" msgstr "" "Project-Id-Version: PhotoCollage 1.4.2\n" "Report-Msgid-Bugs-To: https://github.com/adrienverge/PhotoCollage/issues\n" "POT-Creation-Date: 2014-01-18 02:46-0500\n" "PO-Revision-Date: 2016-04-09 16:20+0200\n" "Last-Translator: Lorenzo Bicci \n" "Language-Team: none \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n!=1;\n" #: photocollage/gtkgui.py:72 photocollage/gtkgui.py:94 msgid "All supported image formats" msgstr "Tutti i formati d’immagine supportati" #: photocollage/gtkgui.py:104 #, python-format msgid "%s image" msgstr "Immagine %s" #: photocollage/gtkgui.py:161 msgid "PhotoCollage" msgstr "PhotoCollage" #: photocollage/gtkgui.py:189 msgid "Add images..." msgstr "Aggiungi immagini..." #: photocollage/gtkgui.py:196 msgid "Save poster..." msgstr "Salva poster..." #: photocollage/gtkgui.py:221 msgid "Regenerate" msgstr "Rigenera" #: photocollage/gtkgui.py:279 #, python-format msgid "" "This image could not be opened:\n" "\"%(imgname)s\"." msgstr "" "Questa immagine non può essere aperta :\n" "\"%(imgname)s\"." #: photocollage/gtkgui.py:285 msgid "Choose images" msgstr "Seleziona immagini" #: photocollage/gtkgui.py:339 photocollage/gtkgui.py:424 msgid "An error occurred while rendering image:" msgstr "Si è verificato un errore durante il rendering dell'immagine:" #: photocollage/gtkgui.py:398 msgid "Save image" msgstr "Salva immagine" #: photocollage/gtkgui.py:622 msgid "Settings" msgstr "Impostazioni" #: photocollage/gtkgui.py:634 msgid "Output image size" msgstr "Dimensione finale dell'immagine" #: photocollage/gtkgui.py:649 msgid "pixels" msgstr "pixel" #: photocollage/gtkgui.py:673 msgid "Apply a template:" msgstr "Applica un modello:" #: photocollage/gtkgui.py:686 msgid "Border" msgstr "Bordo" #: photocollage/gtkgui.py:691 msgid "Thickness:" msgstr "Spessore:" #: photocollage/gtkgui.py:701 msgid "Color:" msgstr "Colore:" #: photocollage/gtkgui.py:740 msgid "Please wait" msgstr "Attendere, prego" #: photocollage/gtkgui.py:749 msgid "Performing image computation..." msgstr "Calcolo dell'immagine in corso..." #: photocollage/gtkgui.py:764 msgid "Error" msgstr "Errore" photocollage-1.4.3/po/fr.po0000664000175000017500000000464712716673056016344 0ustar adrienadrien00000000000000# PhotoCollage translation file # Copyright (C) 2014 Adrien Vergé # This file is distributed under the same license as the PhotoCollage package. # Adrien Vergé, 2014. # msgid "" msgstr "" "Project-Id-Version: PhotoCollage 1.4.2\n" "Report-Msgid-Bugs-To: https://github.com/adrienverge/PhotoCollage/issues\n" "POT-Creation-Date: 2014-01-18 02:46-0500\n" "PO-Revision-Date: 2016-04-07 12:31+0200\n" "Last-Translator: Adrien Vergé \n" "Language-Team: none \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n>1;\n" #: photocollage/gtkgui.py:72 photocollage/gtkgui.py:94 msgid "All supported image formats" msgstr "Tous les formats d'image supportés" #: photocollage/gtkgui.py:104 #, python-format msgid "%s image" msgstr "Image %s" #: photocollage/gtkgui.py:161 msgid "PhotoCollage" msgstr "PhotoCollage" #: photocollage/gtkgui.py:189 msgid "Add images..." msgstr "Ajouter des images..." #: photocollage/gtkgui.py:196 msgid "Save poster..." msgstr "Enregistrer le poster..." #: photocollage/gtkgui.py:221 msgid "Regenerate" msgstr "Regénérer" #: photocollage/gtkgui.py:279 #, python-format msgid "" "This image could not be opened:\n" "\"%(imgname)s\"." msgstr "" "Cette image n'a pas pu être ouverte :\n" "\"%(imgname)s\"." #: photocollage/gtkgui.py:285 msgid "Choose images" msgstr "Choisir les images" #: photocollage/gtkgui.py:339 photocollage/gtkgui.py:424 msgid "An error occurred while rendering image:" msgstr "Une erreur est survenue pendant la composition de l'image :" #: photocollage/gtkgui.py:398 msgid "Save image" msgstr "Enregistrer l'image" #: photocollage/gtkgui.py:622 msgid "Settings" msgstr "Réglages" #: photocollage/gtkgui.py:634 msgid "Output image size" msgstr "Taille de l'image en sortie" #: photocollage/gtkgui.py:649 msgid "pixels" msgstr "pixels" #: photocollage/gtkgui.py:673 msgid "Apply a template:" msgstr "Appliquer un modèle :" #: photocollage/gtkgui.py:686 msgid "Border" msgstr "Bordure" #: photocollage/gtkgui.py:691 msgid "Thickness:" msgstr "Épaisseur :" #: photocollage/gtkgui.py:701 msgid "Color:" msgstr "Couleur :" #: photocollage/gtkgui.py:740 msgid "Please wait" msgstr "Veuillez patienter" #: photocollage/gtkgui.py:749 msgid "Performing image computation..." msgstr "Calcul de l'image en cours..." #: photocollage/gtkgui.py:764 msgid "Error" msgstr "Erreur" photocollage-1.4.3/po/de.po0000664000175000017500000000505512716673056016317 0ustar adrienadrien00000000000000# PhotoCollage translation file # Copyright (C) 2014 Adrien Vergé # Copyright (C) 2015 Frank Quotschalla # Copyright (C) 2015 Vincent Bermel # This file is distributed under the same license as the PhotoCollage package. # Adrien Vergé, 2014. # msgid "" msgstr "" "Project-Id-Version: PhotoCollage 1.4.2\n" "Report-Msgid-Bugs-To: https://github.com/adrienverge/PhotoCollage/issues\n" "POT-Creation-Date: 2016-04-11 22:51+0200\n" "PO-Revision-Date: 2016-04-11 23:13+0200\n" "Last-Translator: Vincent Bermel \n" "Language-Team: none \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Gtranslator 2.91.7\n" #: photocollage/gtkgui.py:72 photocollage/gtkgui.py:94 msgid "All supported image formats" msgstr "Alle unterstützten Bildformate" #: photocollage/gtkgui.py:104 #, python-format msgid "%s image" msgstr "Bild %s" #: photocollage/gtkgui.py:161 msgid "PhotoCollage" msgstr "PhotoCollage" #: photocollage/gtkgui.py:189 msgid "Add images..." msgstr "Bilder hinzufügen" #: photocollage/gtkgui.py:196 msgid "Save poster..." msgstr "Poster abspeichern" #: photocollage/gtkgui.py:221 msgid "Regenerate" msgstr "Neue Anordnung" #: photocollage/gtkgui.py:279 #, python-format msgid "" "This image could not be opened:\n" "\"%(imgname)s\"." msgstr "" "Dieses Bild kann nicht geöffnet werden:\n" "\"%(imgname)s\"." #: photocollage/gtkgui.py:285 msgid "Choose images" msgstr "Bilder auswählen" #: photocollage/gtkgui.py:339 photocollage/gtkgui.py:424 msgid "An error occurred while rendering image:" msgstr "Beim Berechnen des Posters ist ein Fehler aufgetreten:" #: photocollage/gtkgui.py:398 msgid "Save image" msgstr "Poster speichern" #: photocollage/gtkgui.py:622 msgid "Settings" msgstr "Einstellungen" #: photocollage/gtkgui.py:634 msgid "Output image size" msgstr "Ausgabebildgröße" #: photocollage/gtkgui.py:649 msgid "pixels" msgstr "Pixel" #: photocollage/gtkgui.py:673 msgid "Apply a template:" msgstr "Formatvorlage:" #: photocollage/gtkgui.py:686 msgid "Border" msgstr "Rahmen" #: photocollage/gtkgui.py:691 msgid "Thickness:" msgstr "Breite:" #: photocollage/gtkgui.py:701 msgid "Color:" msgstr "Farbe:" #: photocollage/gtkgui.py:740 msgid "Please wait" msgstr "Bitte warten" #: photocollage/gtkgui.py:749 msgid "Performing image computation..." msgstr "Poster wird berechnet..." #: photocollage/gtkgui.py:764 msgid "Error" msgstr "Fehler" photocollage-1.4.3/po/cs.po0000664000175000017500000000501512716673056016330 0ustar adrienadrien00000000000000# PhotoCollage translation file # Copyright (C) 2015 Michal Kovařík # This file is distributed under the same license as the PhotoCollage package. # Michal Kovařík , 2015. # msgid "" msgstr "" "Project-Id-Version: PhotoCollage 1.4.2\n" "Report-Msgid-Bugs-To: https://github.com/adrienverge/PhotoCollage/issues\n" "POT-Creation-Date: 2015-11-21 18:04+0100\n" "PO-Revision-Date: 2016-04-07 14:38+0200\n" "Last-Translator: Michal Kovařík \n" "Language-Team: none \n" "Language: cs\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" "X-Generator: Poedit 1.8.5\n" #: photocollage/gtkgui.py:72 photocollage/gtkgui.py:94 msgid "All supported image formats" msgstr "Všechny podporované formáty" #: photocollage/gtkgui.py:104 #, python-format msgid "%s image" msgstr "%s obrázek" #: photocollage/gtkgui.py:161 msgid "PhotoCollage" msgstr "PhotoCollage" #: photocollage/gtkgui.py:189 msgid "Add images..." msgstr "Přidat obrázky..." #: photocollage/gtkgui.py:196 msgid "Save poster..." msgstr "Uložit pohlednici..." #: photocollage/gtkgui.py:221 msgid "Regenerate" msgstr "Přegenerovat" #: photocollage/gtkgui.py:279 #, python-format msgid "" "This image could not be opened:\n" "\"%(imgname)s\"." msgstr "" "Nelze otevřít tento obrázek:\n" "\"%(imgname)s\"." #: photocollage/gtkgui.py:285 msgid "Choose images" msgstr "Vyberte obrázky" #: photocollage/gtkgui.py:339 photocollage/gtkgui.py:424 msgid "An error occurred while rendering image:" msgstr "Nastala chyba při renderování obrázku:" #: photocollage/gtkgui.py:398 msgid "Save image" msgstr "Uložit obrázek" #: photocollage/gtkgui.py:622 msgid "Settings" msgstr "Nastavení" #: photocollage/gtkgui.py:634 msgid "Output image size" msgstr "Výstupní velikost obrazu" #: photocollage/gtkgui.py:649 msgid "pixels" msgstr "pixelů" #: photocollage/gtkgui.py:673 msgid "Apply a template:" msgstr "Použití šablony:" #: photocollage/gtkgui.py:686 msgid "Border" msgstr "Rámečku" #: photocollage/gtkgui.py:691 msgid "Thickness:" msgstr "Šířka:" #: photocollage/gtkgui.py:701 msgid "Color:" msgstr "Barva:" #: photocollage/gtkgui.py:740 msgid "Please wait" msgstr "Prosím čekejte" #: photocollage/gtkgui.py:749 msgid "Performing image computation..." msgstr "Provádím výpočet obrázku..." #: photocollage/gtkgui.py:764 msgid "Error" msgstr "Chyba" photocollage-1.4.3/po/POTFILES.in0000664000175000017500000000002712716673056017136 0ustar adrienadrien00000000000000photocollage/gtkgui.py photocollage-1.4.3/photocollage/0000775000175000017500000000000012716673066017425 5ustar adrienadrien00000000000000photocollage-1.4.3/photocollage/render.py0000664000175000017500000002302012716673056021252 0ustar adrienadrien00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2014 Adrien Vergé # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import random from threading import Thread import time import PIL.Image import PIL.ImageDraw from photocollage.collage import Photo QUALITY_SKEL = 0 QUALITY_FAST = 1 QUALITY_BEST = 2 class PIL_SUPPORTED_EXTS(object): """File extensions supported by PIL Compiled from: - http://pillow.readthedocs.org/en/2.3.0/handbook/image-file-formats.html - https://github.com/python-imaging/Pillow/blob/master/PIL/*ImagePlugin.py """ RW = { "BMP": ("bmp",), # "EPS": ("ps", "eps",), # doesn't seem to work "GIF": ("gif",), "IM": ("im",), "JPEG": ("jfif", "jpe", "jpg", "jpeg",), "MSP": ("msp",), "PCX": ("pcx",), "PNG": ("png",), "PPM": ("pbm", "pgm", "ppm",), "TGA": ("tga",), "TIFF": ("tif", "tiff",), "WebP": ("webp",), "XBM": ("xbm",), } RO = { "CUR": ("cur",), "DCX": ("dcx",), "FLI": ("fli", "flc",), "FPX": ("fpx",), "GBR": ("gbr",), "ICO": ("ico",), "IPTC/NAA": ("iim",), "PCD": ("pcd",), "PSD": ("psd",), "SGI": ("bw", "rgb", "rgba", "sgi",), "XPM": ("xpm",), } WO = { # "PALM": ("palm",), # doesn't seem to work # "PDF": ("pdf",), # doesn't seem to work } def random_color(): r = random.randrange(256) g = random.randrange(256) b = random.randrange(256) if r + g + b > 0.7 * 3 * 256: r -= 50 g -= 50 b -= 50 return (r, g, b) class BadPhoto(Exception): def __init__(self, photoname): self.photoname = photoname def build_photolist(filelist): ret = [] for name in filelist: try: img = PIL.Image.open(name) except IOError: raise BadPhoto(name) w, h = img.size orientation = 0 try: exif = img._getexif() if 274 in exif: # orientation tag orientation = exif[274] if orientation == 6 or orientation == 8: w, h = h, w except: pass ret.append(Photo(name, w, h, orientation)) return ret cache = {} class RenderingTask(Thread): """Execution thread to do the actual poster rendering Image computation is a heavy task, that can take several seconds. During this, the program might be unresponding. To avoid this, rendering is done is a separated thread. """ def __init__(self, page, border_width=0.01, border_color=(0, 0, 0), quality=QUALITY_FAST, output_file=None, on_update=None, on_complete=None, on_fail=None): super(RenderingTask, self).__init__() self.page = page self.border_width = border_width self.border_color = border_color self.quality = quality self.output_file = output_file self.on_update = on_update self.on_complete = on_complete self.on_fail = on_fail self.canceled = False def abort(self): self.canceled = True def draw_skeleton(self, canvas): for col in self.page.cols: for c in col.cells: if c.is_extension(): continue color = random_color() x, y, w, h = c.content_coords() xy = (x, y) xY = (x, y + h - 1) Xy = (x + w - 1, y) XY = (x + w - 1, y + h - 1) draw = PIL.ImageDraw.Draw(canvas) draw.line(xy + Xy, fill=color) draw.line(xy + xY, fill=color) draw.line(xY + XY, fill=color) draw.line(Xy + XY, fill=color) draw.line(xy + XY, fill=color) draw.line(xY + Xy, fill=color) return canvas def draw_borders(self, canvas): if self.border_width == 0: return W = self.page.w - 1 H = self.page.h - 1 border = self.border_width - 1 color = self.border_color draw = PIL.ImageDraw.Draw(canvas) draw.rectangle((0, 0) + (border, H), color) draw.rectangle((W - border, 0) + (W, H), color) draw.rectangle((0, 0) + (W, border), color) draw.rectangle((0, H - border) + (W, H), color) for col in self.page.cols: # Draw horizontal borders for c in col.cells[1:]: xy = (col.x, c.y - border / 2) XY = (col.x + col.w, c.y + border / 2) draw.rectangle(xy + XY, color) # Draw vertical borders if col.x > 0: for c in col.cells: if not c.is_extension(): xy = (col.x - border / 2, c.y) XY = (col.x + border / 2, c.y + c.h) draw.rectangle(xy + XY, color) return canvas def resize_photo(self, cell, use_cache=False): # If a thumbnail is already in cache, let's use it. But only if it is # bigger than what we need, because we don't want to lose quality. if (use_cache and cell.photo.filename in cache and cache[cell.photo.filename].size[0] >= int(round(cell.w)) and cache[cell.photo.filename].size[1] >= int(round(cell.h))): img = cache[cell.photo.filename].copy() else: img = PIL.Image.open(cell.photo.filename) # Rotate image is EXIF says so if cell.photo.orientation == 3: img = img.rotate(180, expand=True) elif cell.photo.orientation == 6: img = img.rotate(270, expand=True) elif cell.photo.orientation == 8: img = img.rotate(90, expand=True) if self.quality == QUALITY_FAST: method = PIL.Image.NEAREST else: method = PIL.Image.ANTIALIAS shape = img.size[0] * cell.h - img.size[1] * cell.w if shape > 0: # image is too thick img = img.resize((int(round(cell.h * img.size[0] / img.size[1])), int(round(cell.h))), method) elif shape < 0: # image is too tall img = img.resize((int(round(cell.w)), int(round(cell.w * img.size[1] / img.size[0]))), method) else: img = img.resize((int(round(cell.w)), int(round(cell.h))), method) # Save this new image to cache (if it is larger than the previous one) if (use_cache and (cell.photo.filename not in cache or cache[cell.photo.filename].size[0] < img.size[0])): cache[cell.photo.filename] = img if shape > 0: # image is too thick img = img.crop( (int(round((img.size[0] - cell.w) / 2)), 0, int(round((img.size[0] + cell.w) / 2)), int(round(cell.h)))) elif shape < 0: # image is too tall img = img.crop( (0, int(round((img.size[1] - cell.h) / 2)), int(round(cell.w)), int(round((img.size[1] + cell.h) / 2)))) return img def paste_photo(self, canvas, cell, img): canvas.paste(img, (int(round(cell.x)), int(round(cell.y)))) return canvas def run(self): try: canvas = PIL.Image.new( "RGB", (int(self.page.w), int(self.page.h)), "white") self.draw_skeleton(canvas) self.draw_borders(canvas) if self.quality != QUALITY_SKEL: n = sum([len([cell for cell in col.cells if not cell.is_extension()]) for col in self.page.cols]) i = 0.0 if self.on_update: self.on_update(canvas, 0.0) last_update = time.time() for col in self.page.cols: for c in col.cells: if self.canceled: # someone clicked "abort" return if c.is_extension(): continue img = self.resize_photo(c, use_cache=True) self.paste_photo(canvas, c, img) # Only needed for interactive rendering if self.on_update: self.draw_borders(canvas) i += 1 now = time.time() if self.on_update and now > last_update + 0.1: self.on_update(canvas, i / n) last_update = now self.draw_borders(canvas) if self.output_file: canvas.save(self.output_file) if self.on_complete: self.on_complete(canvas) except Exception as e: if self.on_fail: self.on_fail(e) photocollage-1.4.3/photocollage/gtkgui.py0000664000175000017500000007140412716673056021276 0ustar adrienadrien00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2013 Adrien Vergé # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import copy import gettext from io import BytesIO import math import os.path import random import sys import cairo import gi gi.require_version('Gtk', '3.0') # noqa from gi.repository import Gtk, Gdk, GObject from six.moves import urllib # Python 2 backward compatibility from photocollage import APP_NAME, artwork, collage, render from photocollage.render import PIL_SUPPORTED_EXTS as EXTS gettext.textdomain(APP_NAME) _ = gettext.gettext _n = gettext.ngettext # xgettext --keyword=_n:1,2 -o po/photocollage.pot $(find . -name '*.py') # cp po/photocollage.pot po/fr.po # msgfmt -o po/fr.mo po/fr.po def pil_image_to_cairo_surface(src): # TODO: cairo.ImageSurface.create_for_data() is not yet available in # Python 3, so we use PNG as an intermediate. buf = BytesIO() src.save(buf, "png") buf.seek(0) surface = cairo.ImageSurface.create_from_png(buf) buf.close() return surface def get_all_save_image_exts(): all_types = dict(list(EXTS.RW.items()) + list(EXTS.WO.items())) all = [] for type in all_types: for ext in all_types[type]: all.append(ext) return all def set_open_image_filters(dialog): """Set our own filter because Gtk.FileFilter.add_pixbuf_formats() contains formats not supported by PIL. """ # Do not show the filter to the user, just limit selectable files imgfilter = Gtk.FileFilter() imgfilter.set_name(_("All supported image formats")) all_types = dict(list(EXTS.RW.items()) + list(EXTS.RO.items())) for type in all_types: for ext in all_types[type]: imgfilter.add_pattern("*." + ext) imgfilter.add_pattern("*." + ext.upper()) dialog.add_filter(imgfilter) dialog.set_filter(imgfilter) def set_save_image_filters(dialog): """Set our own filter because Gtk.FileFilter.add_pixbuf_formats() contains formats not supported by PIL. """ all_types = dict(list(EXTS.RW.items()) + list(EXTS.WO.items())) filters = [] filters.append(Gtk.FileFilter()) flt = filters[-1] flt.set_name(_("All supported image formats")) for ext in get_all_save_image_exts(): flt.add_pattern("*." + ext) flt.add_pattern("*." + ext.upper()) dialog.add_filter(flt) dialog.set_filter(flt) for type in all_types: filters.append(Gtk.FileFilter()) flt = filters[-1] name = _("%s image") % type name += " (." + ", .".join(all_types[type]) + ")" flt.set_name(name) for ext in all_types[type]: flt.add_pattern("*." + ext) flt.add_pattern("*." + ext.upper()) dialog.add_filter(flt) def gtk_run_in_main_thread(fn): def my_fn(*args, **kwargs): GObject.idle_add(fn, *args, **kwargs) return my_fn class UserCollage(object): """Represents a user-defined collage A UserCollage contains a list of photos (referenced by filenames) and a collage.Page object describing their layout in a final poster. """ def __init__(self, photolist): self.photolist = photolist def make_page(self, opts): # Define the output image height / width ratio ratio = 1.0 * opts.out_h / opts.out_w # Compute a good number of columns. It depends on the ratio, the number # of images and the average ratio of these images. According to my # calculations, the number of column should be inversely proportional # to the square root of the output image ratio, and proportional to the # square root of the average input images ratio. avg_ratio = (sum(1.0 * photo.h / photo.w for photo in self.photolist) / len(self.photolist)) # Virtual number of images: since ~ 1 image over 3 is in a multi-cell # (i.e. takes two columns), it takes the space of 4 images. # So it's equivalent to 1/3 * 4 + 2/3 = 2 times the number of images. virtual_no_imgs = 2 * len(self.photolist) no_cols = int(round(math.sqrt(avg_ratio / ratio * virtual_no_imgs))) self.page = collage.Page(1.0, ratio, no_cols) random.shuffle(self.photolist) for photo in self.photolist: self.page.add_cell(photo) self.page.adjust() def duplicate(self): return UserCollage(copy.copy(self.photolist)) class PhotoCollageWindow(Gtk.Window): TARGET_TYPE_TEXT = 1 TARGET_TYPE_URI = 2 def __init__(self): super(PhotoCollageWindow, self).__init__(title=_("PhotoCollage")) self.history = [] self.history_index = 0 class Options(object): def __init__(self): self.border_w = 0.01 self.border_c = "black" self.out_w = 800 self.out_h = 600 self.opts = Options() self.make_window() def make_window(self): self.set_border_width(10) box_window = Gtk.Box(spacing=10, orientation=Gtk.Orientation.VERTICAL) self.add(box_window) # ----------------------- # Input and output pan # ----------------------- box = Gtk.Box(spacing=6, orientation=Gtk.Orientation.HORIZONTAL) box_window.pack_start(box, False, False, 0) self.btn_choose_images = Gtk.Button(label=_("Add images...")) self.btn_choose_images.set_image(Gtk.Image.new_from_stock( Gtk.STOCK_OPEN, Gtk.IconSize.LARGE_TOOLBAR)) self.btn_choose_images.set_always_show_image(True) self.btn_choose_images.connect("clicked", self.choose_images) box.pack_start(self.btn_choose_images, False, False, 0) self.btn_save = Gtk.Button(label=_("Save poster...")) self.btn_save.set_image(Gtk.Image.new_from_stock( Gtk.STOCK_SAVE_AS, Gtk.IconSize.LARGE_TOOLBAR)) self.btn_save.set_always_show_image(True) self.btn_save.connect("clicked", self.save_poster) box.pack_start(self.btn_save, False, False, 0) # ----------------------- # Tools pan # ----------------------- box.pack_start(Gtk.SeparatorToolItem(), True, True, 0) self.btn_undo = Gtk.Button() self.btn_undo.set_image(Gtk.Image.new_from_stock( Gtk.STOCK_UNDO, Gtk.IconSize.LARGE_TOOLBAR)) self.btn_undo.connect("clicked", self.select_prev_layout) box.pack_start(self.btn_undo, False, False, 0) self.lbl_history_index = Gtk.Label(" ") box.pack_start(self.lbl_history_index, False, False, 0) self.btn_redo = Gtk.Button() self.btn_redo.set_image(Gtk.Image.new_from_stock( Gtk.STOCK_REDO, Gtk.IconSize.LARGE_TOOLBAR)) self.btn_redo.connect("clicked", self.select_next_layout) box.pack_start(self.btn_redo, False, False, 0) self.btn_new_layout = Gtk.Button(label=_("Regenerate")) self.btn_new_layout.set_image(Gtk.Image.new_from_stock( Gtk.STOCK_REFRESH, Gtk.IconSize.LARGE_TOOLBAR)) self.btn_new_layout.set_always_show_image(True) self.btn_new_layout.connect("clicked", self.regenerate_layout) box.pack_start(self.btn_new_layout, False, False, 0) box.pack_start(Gtk.SeparatorToolItem(), True, True, 0) self.btn_settings = Gtk.Button() self.btn_settings.set_image(Gtk.Image.new_from_stock( Gtk.STOCK_PREFERENCES, Gtk.IconSize.LARGE_TOOLBAR)) self.btn_settings.set_always_show_image(True) self.btn_settings.connect("clicked", self.set_settings) box.pack_end(self.btn_settings, False, False, 0) # ------------------- # Image preview pan # ------------------- box = Gtk.Box(spacing=10) box_window.pack_start(box, True, True, 0) self.img_preview = ImagePreviewArea(self) self.img_preview.set_size_request(600, 400) self.img_preview.connect("drag-data-received", self.on_drag) self.img_preview.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) targets = Gtk.TargetList.new([]) targets.add_text_targets(PhotoCollageWindow.TARGET_TYPE_TEXT) targets.add_uri_targets(PhotoCollageWindow.TARGET_TYPE_URI) self.img_preview.drag_dest_set_target_list(targets) box.pack_start(self.img_preview, True, True, 0) self.btn_save.set_sensitive(False) self.btn_undo.set_sensitive(False) self.btn_redo.set_sensitive(False) self.update_photolist([]) def update_photolist(self, new_images): try: photolist = [] if self.history_index < len(self.history): photolist = copy.copy( self.history[self.history_index].photolist) photolist.extend(render.build_photolist(new_images)) if len(photolist) > 0: new_collage = UserCollage(photolist) new_collage.make_page(self.opts) self.render_from_new_collage(new_collage) else: self.update_tool_buttons() except render.BadPhoto as e: dialog = ErrorDialog( self, _("This image could not be opened:\n\"%(imgname)s\".") % {"imgname": e.photoname}) dialog.run() dialog.destroy() def choose_images(self, button): dialog = Gtk.FileChooserDialog(_("Choose images"), button.get_toplevel(), Gtk.FileChooserAction.OPEN, select_multiple=True) dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) dialog.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK) set_open_image_filters(dialog) if dialog.run() == Gtk.ResponseType.OK: files = dialog.get_filenames() dialog.destroy() self.update_photolist(files) else: dialog.destroy() def on_drag(self, widget, drag_context, x, y, data, info, time): if info == PhotoCollageWindow.TARGET_TYPE_TEXT: files = data.get_text().splitlines() elif info == PhotoCollageWindow.TARGET_TYPE_URI: # Can only handle local URIs files = [f for f in data.get_uris() if f.startswith("file://")] for i in range(len(files)): if files[i].startswith("file://"): files[i] = urllib.parse.unquote(files[i][7:]) self.update_photolist(files) def render_preview(self): collage = self.history[self.history_index] # If the desired ratio changed in the meantime (e.g. from landscape to # portrait), it needs to be re-updated collage.page.target_ratio = 1.0 * self.opts.out_h / self.opts.out_w collage.page.adjust_cols_heights() w = self.img_preview.get_allocation().width h = self.img_preview.get_allocation().height collage.page.scale_to_fit(w, h) # Display a "please wait" dialog and do the job. compdialog = ComputingDialog(self) def on_update(img, fraction_complete): self.img_preview.set_collage(img, collage) compdialog.update(fraction_complete) def on_complete(img): self.img_preview.set_collage(img, collage) compdialog.destroy() self.btn_save.set_sensitive(True) def on_fail(exception): dialog = ErrorDialog(self, "%s:\n\n%s" % ( _("An error occurred while rendering image:"), exception)) compdialog.destroy() dialog.run() dialog.destroy() self.btn_save.set_sensitive(False) t = render.RenderingTask( collage.page, border_width=self.opts.border_w * max(collage.page.w, collage.page.h), border_color=self.opts.border_c, on_update=gtk_run_in_main_thread(on_update), on_complete=gtk_run_in_main_thread(on_complete), on_fail=gtk_run_in_main_thread(on_fail)) t.start() response = compdialog.run() if response == Gtk.ResponseType.CANCEL: t.abort() compdialog.destroy() def render_from_new_collage(self, collage): self.history.append(collage) self.history_index = len(self.history) - 1 self.update_tool_buttons() self.render_preview() def regenerate_layout(self, button=None): new_collage = self.history[self.history_index].duplicate() new_collage.make_page(self.opts) self.render_from_new_collage(new_collage) def select_prev_layout(self, button): self.history_index -= 1 self.update_tool_buttons() self.render_preview() def select_next_layout(self, button): self.history_index += 1 self.update_tool_buttons() self.render_preview() def set_settings(self, button): dialog = SettingsDialog(self) response = dialog.run() if response == Gtk.ResponseType.OK: dialog.apply_opts(self.opts) dialog.destroy() if self.history: self.render_preview() else: dialog.destroy() def save_poster(self, button): collage = self.history[self.history_index] enlargement = float(self.opts.out_w) / collage.page.w collage.page.scale(enlargement) dialog = Gtk.FileChooserDialog(_("Save image"), button.get_toplevel(), Gtk.FileChooserAction.SAVE) dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) dialog.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK) dialog.set_do_overwrite_confirmation(True) set_save_image_filters(dialog) if dialog.run() != Gtk.ResponseType.OK: dialog.destroy() return savefile = dialog.get_filename() base, ext = os.path.splitext(savefile) if ext == "" or not ext[1:].lower() in get_all_save_image_exts(): savefile += ".jpg" dialog.destroy() # Display a "please wait" dialog and do the job. compdialog = ComputingDialog(self) def on_update(img, fraction_complete): compdialog.update(fraction_complete) def on_complete(img): compdialog.destroy() def on_fail(exception): dialog = ErrorDialog(self, "%s:\n\n%s" % ( _("An error occurred while rendering image:"), exception)) compdialog.destroy() dialog.run() dialog.destroy() t = render.RenderingTask( collage.page, output_file=savefile, border_width=self.opts.border_w * max(collage.page.w, collage.page.h), border_color=self.opts.border_c, on_update=gtk_run_in_main_thread(on_update), on_complete=gtk_run_in_main_thread(on_complete), on_fail=gtk_run_in_main_thread(on_fail)) t.start() response = compdialog.run() if response == Gtk.ResponseType.CANCEL: t.abort() compdialog.destroy() def update_tool_buttons(self): self.btn_undo.set_sensitive(self.history_index > 0) self.btn_redo.set_sensitive(self.history_index < len(self.history) - 1) if self.history_index < len(self.history): self.lbl_history_index.set_label(str(self.history_index + 1)) else: self.lbl_history_index.set_label(" ") self.btn_save.set_sensitive( self.history_index < len(self.history)) self.btn_new_layout.set_sensitive( self.history_index < len(self.history)) class ImagePreviewArea(Gtk.DrawingArea): """Area to display the poster preview and react to user actions""" INSENSITIVE, FLYING, SWAPPING = range(3) def __init__(self, parent): super(ImagePreviewArea, self).__init__() self.parent = parent parse, color = Gdk.Color.parse("#888888") self.modify_bg(Gtk.StateType.NORMAL, color) # http://www.pygtk.org/pygtk2tutorial/sec-EventHandling.html # https://developer.gnome.org/gdk3/stable/gdk3-Events.html#GdkEventMask self.connect("draw", self.draw) self.connect("motion-notify-event", self.motion_notify_event) self.connect("leave-notify-event", self.motion_notify_event) self.connect("button-press-event", self.button_press_event) self.connect("button-release-event", self.button_release_event) self.set_events(Gdk.EventMask.EXPOSURE_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK | Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.POINTER_MOTION_MASK) self.image = None self.mode = self.INSENSITIVE class SwapEnd(object): def __init__(self, cell=None, x=0, y=0): self.cell = cell self.x = x self.y = y self.x, self.y = 0, 0 self.swap_origin = SwapEnd() self.swap_dest = SwapEnd() def set_collage(self, image, collage): self.image = pil_image_to_cairo_surface(image) # The Collage object must be deeply copied. Otherwise, swapping photos # in a new page would also affect the original page (in history). # The deep copy is done here (not in button_release_event) because # references to cells are gathered in other functions, so that making # the copy at the end would invalidate these references. self.collage = copy.deepcopy(collage) self.mode = self.FLYING self.queue_draw() def get_image_offset(self): return (round((self.get_allocation().width - self.image.get_width()) / 2.0), round((self.get_allocation().height - self.image.get_height()) / 2.0)) def get_pos_in_image(self, x, y): if self.image is not None: x0, y0 = self.get_image_offset() return (int(round(x - x0)), int(round(y - y0))) return (int(round(x)), int(round(y))) def paint_image_border(self, context, cell, dash=None): x0, y0 = self.get_image_offset() context.set_source_rgb(1.0, 1.0, 0.0) context.set_line_width(2) if dash is not None: context.set_dash(dash) context.rectangle(x0 + cell.x + 1, y0 + cell.y + 1, cell.w - 2, cell.h - 2) context.stroke() def paint_image_delete_button(self, context, cell): x0, y0 = self.get_image_offset() x = x0 + cell.x + cell.w - 12 y = y0 + cell.y + 12 context.arc(x, y, 8, 0, 6.2832) context.set_source_rgb(0.8, 0.0, 0.0) context.fill() context.arc(x, y, 8, 0, 6.2832) context.set_source_rgb(0.0, 0.0, 0.0) context.set_line_width(1) context.move_to(x - 4, y - 4) context.line_to(x + 4, y + 4) context.move_to(x - 4, y + 4) context.line_to(x + 4, y - 4) context.stroke() def draw(self, widget, context): if self.image is not None: x0, y0 = self.get_image_offset() context.set_source_surface(self.image, x0, y0) context.paint() if self.mode == self.FLYING: cell = self.collage.page.get_cell_at_position(self.x, self.y) if cell: self.paint_image_border(context, cell) self.paint_image_delete_button(context, cell) elif self.mode == self.SWAPPING: self.paint_image_border(context, self.swap_origin.cell, (3, 3)) cell = self.collage.page.get_cell_at_position(self.x, self.y) if cell and cell != self.swap_origin.cell: self.paint_image_border(context, cell, (3, 3)) else: # Display the drag & drop image dnd_image = artwork.load_cairo_surface(artwork.ICON_DRAG_AND_DROP) context.set_source_surface( dnd_image, round((self.get_allocation().width - dnd_image.get_width()) / 2.0), round((self.get_allocation().height - dnd_image.get_height()) / 2.0)) context.paint() return False def motion_notify_event(self, widget, event): self.x, self.y = self.get_pos_in_image(event.x, event.y) widget.queue_draw() def button_press_event(self, widget, event): if self.mode == self.FLYING: x, y = self.get_pos_in_image(event.x, event.y) cell = self.collage.page.get_cell_at_position(x, y) if not cell: return # Has the user clicked the delete button? dist = (cell.x + cell.w - 12 - x) ** 2 + (cell.y + 12 - y) ** 2 if dist <= 8 * 8: self.collage.photolist.remove(cell.photo) if self.collage.photolist: self.collage.make_page(self.parent.opts) self.parent.render_from_new_collage(self.collage) else: self.image = None self.mode = self.INSENSITIVE self.parent.history_index = len(self.parent.history) self.parent.update_tool_buttons() # Otherwise, the user wants to swap this image with another else: self.swap_origin.x, self.swap_origin.y = x, y self.swap_origin.cell = cell self.mode = self.SWAPPING widget.queue_draw() def button_release_event(self, widget, event): if self.mode == self.SWAPPING: self.swap_dest.x, self.swap_dest.y = \ self.get_pos_in_image(event.x, event.y) self.swap_dest.cell = self.collage.page.get_cell_at_position( self.swap_dest.x, self.swap_dest.y) if self.swap_dest.cell \ and self.swap_origin.cell != self.swap_dest.cell: self.collage.page.swap_photos(self.swap_origin.cell, self.swap_dest.cell) self.parent.render_from_new_collage(self.collage) self.mode = self.FLYING widget.queue_draw() class SettingsDialog(Gtk.Dialog): def __init__(self, parent): super(SettingsDialog, self).__init__( _("Settings"), parent, 0, (Gtk.STOCK_OK, Gtk.ResponseType.OK, Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)) self.set_border_width(10) self.selected_border_color = parent.opts.border_c box = self.get_content_area() vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) box.add(vbox) label = Gtk.Label(xalign=0) label.set_markup("%s" % _("Output image size")) vbox.pack_start(label, False, False, 0) box = Gtk.Box(spacing=6) vbox.pack_start(box, False, False, 0) self.etr_outw = Gtk.Entry(text=str(parent.opts.out_w)) self.etr_outw.connect("changed", self.validate_int) self.etr_outw.last_valid_text = self.etr_outw.get_text() box.pack_start(self.etr_outw, False, False, 0) box.pack_start(Gtk.Label("×", xalign=0), False, False, 0) self.etr_outh = Gtk.Entry(text=str(parent.opts.out_h)) self.etr_outh.connect("changed", self.validate_int) self.etr_outh.last_valid_text = self.etr_outh.get_text() box.pack_start(self.etr_outh, False, False, 0) box.pack_end(Gtk.Label(_("pixels"), xalign=0), False, False, 0) templates = ( ("", None), ("800 × 600", (800, 600)), ("1600 × 1200", (1600, 1200)), ("A4 landscape (300ppi)", (3508, 2480)), ("A4 portrait (300ppi)", (2480, 3508)), ("A3 landscape (300ppi)", (4960, 3508)), ("A3 portrait (300ppi)", (3508, 4960)), ("US-Letter landscape (300ppi)", (3300, 2550)), ("US-Letter portrait (300ppi)", (2550, 3300)), ) def apply_template(combo): t = combo.get_model()[combo.get_active_iter()][1] if t: dims = dict(templates)[t] self.etr_outw.set_text(str(dims[0])) self.etr_outh.set_text(str(dims[1])) self.cmb_template.set_active(0) box = Gtk.Box(spacing=6) vbox.pack_start(box, False, False, 0) box.pack_start(Gtk.Label(_("Apply a template:"), xalign=0), True, True, 0) self.cmb_template = Gtk.ComboBoxText() for t, d in templates: self.cmb_template.append(t, t) self.cmb_template.set_active(0) self.cmb_template.connect("changed", apply_template) box.pack_start(self.cmb_template, False, False, 0) vbox.pack_start(Gtk.SeparatorToolItem(), True, True, 0) label = Gtk.Label(xalign=0) label.set_markup("%s" % _("Border")) vbox.pack_start(label, False, False, 0) box = Gtk.Box(spacing=6) vbox.pack_start(box, False, False, 0) label = Gtk.Label(_("Thickness:"), xalign=0) box.pack_start(label, False, False, 0) self.etr_border = Gtk.Entry(text=str(100.0 * parent.opts.border_w)) self.etr_border.connect("changed", self.validate_float) self.etr_border.last_valid_text = self.etr_border.get_text() self.etr_border.set_width_chars(2) box.pack_start(self.etr_border, False, False, 0) label = Gtk.Label("%", xalign=0) box.pack_start(label, False, False, 0) label = Gtk.Label(_("Color:"), xalign=1) box.pack_start(label, True, True, 0) self.colorbutton = Gtk.ColorButton() color = Gdk.RGBA() color.parse(parent.opts.border_c) self.colorbutton.set_rgba(color) box.pack_end(self.colorbutton, False, False, 0) vbox.pack_start(Gtk.SeparatorToolItem(), True, True, 0) self.show_all() def validate_int(self, entry): entry_text = entry.get_text() or '0' try: int(entry_text) entry.last_valid_text = entry_text except ValueError: entry.set_text(entry.last_valid_text) def validate_float(self, entry): entry_text = entry.get_text() or '0' try: float(entry_text) entry.last_valid_text = entry_text except ValueError: entry.set_text(entry.last_valid_text) def apply_opts(self, opts): opts.out_w = int(self.etr_outw.get_text() or '1') opts.out_h = int(self.etr_outh.get_text() or '1') opts.border_w = float(self.etr_border.get_text() or '0') / 100.0 opts.border_c = self.colorbutton.get_rgba().to_string() class ComputingDialog(Gtk.Dialog): """Simple "please wait" dialog, with a "cancel" button""" def __init__(self, parent): super(ComputingDialog, self).__init__( _("Please wait"), parent, 0, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)) self.set_default_size(300, -1) self.set_border_width(10) box = self.get_content_area() vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) box.add(vbox) label = Gtk.Label(_("Performing image computation...")) vbox.pack_start(label, True, True, 0) self.progressbar = Gtk.ProgressBar() self.progressbar.set_fraction(0) vbox.pack_start(self.progressbar, True, True, 0) self.show_all() def update(self, fraction): self.progressbar.set_fraction(fraction) class ErrorDialog(Gtk.Dialog): def __init__(self, parent, message): super(ErrorDialog, self).__init__(_("Error"), parent, 0, (Gtk.STOCK_OK, Gtk.ResponseType.OK)) self.set_border_width(10) box = self.get_content_area() box.add(Gtk.Label(message)) self.show_all() def main(): # Enable threading. Without that, threads hang! GObject.threads_init() win = PhotoCollageWindow() win.connect("delete-event", Gtk.main_quit) win.show_all() # If arguments are given, treat them as input images if len(sys.argv) > 1: win.update_photolist(sys.argv[1:]) Gtk.main() photocollage-1.4.3/photocollage/collage.py0000664000175000017500000004254012716673056021411 0ustar adrienadrien00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2014 Adrien Vergé # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import random """ Summary of collage objects: ---------------------- | | | | | Page | The "Page" object represents the whole page that | | will give the final assembled image. | | ---------------------- ---------------------- | | | | | |Column| | | | |Column| A page is divided into columns. |Column| | | | | | | ---------------------- ---------------------- | Cell | Cell | Each column contains cells. When a |------| x--|----- CellExtent is located in several columns, | Cell |-------------| its "extended" flag is set, and a |------| Cell | | CellExtent object is added to the | Cell | | Cell | column on the right to reserve the ---------------------- place. ,------> Photo / ,---------> Photo ---------------------- | | | Each cell is associated |------| | to a photo. | |-------------| |------| | |----> Photo | | | | ---------------------- `----------> Photo The layout placing process is divided in three phases. Phase A: Fill columns with photos. Photos are added to columns, one by one, until there are no more photos. Each new photo is put in the smallest column, so as to have balanced columns. If two columns have approximately the same height, a photo can be "extended" to fit two columns. In this case, the "Cell" object is put in the first column, and the second column takes a "CellExtent" object to reserve the taken space. Phase B: Set all columns to same height. A global common height is computed for all columns, and every of them is stressed or extended to this common length. This can result in a decrease or increase in columns' width. Phase C: Adapt columns' width. Since cells in a column may have different widths, each column width is set to the smallest width amongst its images. """ class Photo(object): def __init__(self, filename, w, h, orientation=0): self.filename = filename self.w = w self.h = h self.orientation = orientation @property def ratio(self): return float(self.h) / float(self.w) class Cell(object): """Represents a cell in a column Properties: <- x -><- w -> ---------------------- ^ | | | |y |------| | | | |-------------| v |------| Cell | | ^ | | | | |h ---------------------- v """ def __init__(self, parents, photo): self.parents = parents self.photo = photo self.extent = None self.h = self.w * self.wanted_ratio def __repr__(self): """Representation of the cell in ASCII art""" end = "]" if self.extent is not None: end = "--" return "[%d %d%s" % (self.w, self.h, end) @property def x(self): return self.parents[0].x @property def y(self): """Returns the cell's y coordinate It assumes that the cell is in a single column, so it is the previous cell's y + h. """ prev = None for c in self.parents[0].cells: if self is c: if prev: return prev.y + prev.h return 0 prev = c @property def w(self): return sum(c.w for c in self.parents) @property def ratio(self): return self.h / self.w @property def wanted_ratio(self): return self.photo.ratio def scale(self, alpha): self.h *= alpha def is_extended(self): return hasattr(self, 'extent') and self.extent is not None def is_extension(self): return isinstance(self, CellExtent) def content_coords(self): """Returns the coordinates of the contained image These are computed in order not to loose space, so the content area will always be greater than the cell itself. It is the space taken by the contained image if it wasn't cropped. """ # If the contained image is too thick to fit if self.wanted_ratio < self.ratio: h = self.h w = self.h / self.wanted_ratio y = self.y x = self.x - (w - self.w) / 2.0 # If the contained image is too tall to fit elif self.wanted_ratio > self.ratio: w = self.w h = self.w * self.wanted_ratio x = self.x y = self.y - (h - self.h) / 2.0 else: w = self.w h = self.h x = self.x y = self.y return x, y, w, h def top_neighbor(self): """Returns the cell above this one""" prev = None for c in self.parents[0].cells: if self is c: return prev prev = c def bottom_neighbor(self): """Returns the cell below this one""" prev = None for c in reversed(self.parents[0].cells): if self is c: return prev prev = c class CellExtent(Cell): def __init__(self, cell): self.origin = cell self.origin.extent = self def __repr__(self): """Representation of the cell in ASCII art""" return "------]" @property def parents(self): return (self.origin.parents[1],) @property def photo(self): return self.origin.photo @property def y(self): return self.origin.y @property def h(self): return self.origin.h def scale(self, alpha): pass class Column(object): """Represents a column in a page Properties: <----- x ----><-- w -> ---------------------- ^ | | | | | | | | | | | |Column| h | | | | -------| | | | | |------- v -------- """ def __init__(self, parent, w): self.parent = parent self.cells = [] self.w = w def __repr__(self): """Representation of the column in ASCII art""" return "\n".join(c.__repr__() for c in self.cells) @property def h(self): """Returns the column's total height This is not simply the sum of its cells heights, because there can be empty spaces between cells. """ if not self.cells: return 0 return self.cells[-1].y + self.cells[-1].h @property def x(self): x = 0 for c in self.parent.cols: if self is c: break x += c.w return x def scale(self, alpha): self.w *= alpha for c in self.cells: c.scale(alpha) def left_neighbor(self): """Returns the column on the left of this one""" prev = None for c in self.parent.cols: if self is c: return prev prev = c def right_neighbor(self): """Returns the column on the right of this one""" prev = None for c in reversed(self.parent.cols): if self is c: return prev prev = c def adjust_height(self, target_h): """Set the column's height to a given value by resizing cells""" # First, make groups of "movable" cells. Since cell extents are not # movable, these groups only contain pure cell objects. We only resize # those groups. class Group(object): def __init__(self, y): self.y = y self.h = 0 self.cells = [] groups = [] groups.append(Group(0)) for c in self.cells: # While a cell extent is not reached, keep add cells to the group if not c.is_extension(): groups[-1].cells.append(c) else: # Close current group and create a new one groups[-1].h = c.y - groups[-1].y groups.append(Group(c.y + c.h)) groups[-1].h = target_h - groups[-1].y # Adjust height for each group independently for group in groups: if not group.cells: continue alpha = group.h / sum(c.h for c in group.cells) for c in group.cells: c.h = c.h * alpha class Page(object): """Represents a whole page Properties: <-------- w --------> ---------------------- ^ | | | | | | Page | h | | | | | ---------------------- v """ def __init__(self, w, target_ratio, no_cols): self.target_ratio = target_ratio col_w = float(w)/no_cols self.cols = [] for i in range(no_cols): self.cols.append(Column(self, col_w)) def __repr__(self): """Representation of the page in ASCII art Returns something like: [62 52] [125 134-- ------] [62 87] [62 47] [62 66] [125 132-- [62 45] [62 46] ------] [62 49] ------] [62 78] ------] [62 49] [62 45] [125 102-- ------] [62 49] [62 65] [125 135-- [62 85] [62 53] [125 91-- [125 89-- [62 64] ------] """ lines = [] n = 0 end = False while not end: lines.append("") end = True for col in self.cols: cells = col.__repr__().split("\n") w = max(len(cell) for cell in cells) if col != self.cols[-1]: w += 1 cell = w * " " if n < len(cells): cell = cells[n] + (w - len(cells[n])) * " " if n < len(cells) - 1: end = False lines[-1] += cell n += 1 return "\n".join(lines) @property def no_cols(self): return len(self.cols) @property def w(self): return sum(c.w for c in self.cols) @property def h(self): return max(c.h for c in self.cols) @property def ratio(self): return self.h / self.w def scale(self, alpha): for c in self.cols: c.scale(alpha) def scale_to_fit(self, max_w, max_h=None): if max_h is None or self.w * max_h > self.h * max_w: self.scale(max_w / self.w) else: self.scale(max_h / self.h) def next_free_col(self): """Returns the column with lowest height""" minimum = min(c.h for c in self.cols) candidates = [] for c in self.cols: if c.h == minimum: candidates.append(c) return random.choice(candidates) def add_cell_single_col(self, col, photo): col.cells.append(Cell((col,), photo)) def add_cell_multi_col(self, col1, col2, photo): cell = Cell((col1, col2), photo) extent = CellExtent(cell) col1.cells.append(cell) col2.cells.append(extent) def add_cell(self, photo): """Add a new cell in the best computed place If possible, and if it's worth, make a "multiple-column" cell. """ col = self.next_free_col() left = col.left_neighbor() right = col.right_neighbor() if 2 * random.random() > photo.ratio: if left and abs(col.h - left.h) < 0.5 * col.w: return self.add_cell_multi_col(left, col, photo) elif right and abs(col.h - right.h) < 0.5 * col.w: return self.add_cell_multi_col(col, right, photo) self.add_cell_single_col(col, photo) def remove_empty_cols(self): i = 0 while i < len(self.cols): if len(self.cols[i].cells) == 0: self.cols.pop(i) else: i += 1 def remove_bottom_holes(self): """Remove holes created by extended cells Example (case A): The bottom-right cell should be extended to fill the hole. ---------------------- ---------------------- | | | | | | | | | |-------------| | |-------------| |------| | |------| | | |-------------- | |-------------- | | | ^ | | ^ | | --------------- hole -------- hole -------- Example (case B): The bottom cell should be moved under the other extended cell. ---------------------- ---------------------- | | | | | | | | |------|-------------| |-------------|------| | | | | | | |--------------------- ---------------------| | | <-- hole hole -> | | --------------- --------------- """ for col in self.cols: cell = col.cells[-1] if cell == col.cells[0]: continue # Case A # If cell is not extended, is below an extended cell and has no # neighbour under the latter, it should be extended. if not cell.is_extended() and not cell.is_extension(): # Case A1 if cell.top_neighbor().is_extended() \ and cell.top_neighbor().extent \ .bottom_neighbor() is None: # Extend cell to right extent = CellExtent(cell) col.right_neighbor().cells.append(extent) cell.parents = (col, col.right_neighbor()) # Case A2 elif cell.top_neighbor().is_extension() \ and cell.top_neighbor().origin \ .bottom_neighbor() is None: # Extend cell to left col.cells.remove(cell) col.left_neighbor().cells.append(cell) extent = CellExtent(cell) col.cells.append(extent) cell.parents = (col.left_neighbor(), col) # Case B # If cell is extended and one of the cells above is extended too, # the bottom cell should be placed right below the top one. elif cell.is_extended() and cell.extent.bottom_neighbor() is None: # Case B1 if cell.extent.top_neighbor().is_extended() \ and cell.extent.top_neighbor().extent \ .bottom_neighbor() is None: # Move cell to right col.cells.remove(cell) col.right_neighbor().cells.remove(cell.extent) col.right_neighbor().cells.append(cell) col.right_neighbor().right_neighbor().cells \ .append(cell.extent) cell.parents = (col.right_neighbor(), col.right_neighbor().right_neighbor()) # Case B2 elif cell.top_neighbor().is_extension() \ and cell.top_neighbor().origin \ .bottom_neighbor() is None: # Move cell to left col.cells.remove(cell) col.right_neighbor().cells.remove(cell.extent) col.left_neighbor().cells.append(cell) col.cells.append(cell.extent) cell.parents = (col.left_neighbor(), col) def adjust_cols_heights(self): """Set all columns' heights to same value by shrinking them""" target_h = self.w * self.target_ratio for c in self.cols: c.adjust_height(target_h) def adjust(self): self.remove_empty_cols() self.remove_bottom_holes() self.adjust_cols_heights() def get_cell_at_position(self, x, y): for col in self.cols: if x >= col.x and x < col.x + col.w: for cell in col.cells: if y >= cell.y and y < cell.y + cell.h: if cell.is_extension(): return cell.origin return cell return None def swap_photos(self, cell1, cell2): cell1.photo, cell2.photo = cell2.photo, cell1.photo photocollage-1.4.3/photocollage/artwork.py0000664000175000017500000001720212716673056021471 0ustar adrienadrien00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2014 Adrien Vergé # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import base64 from io import BytesIO import cairo from gi.repository import GdkPixbuf # Generated with: # import base64 # with open("data/icons/dragndrop.png", "rb") as f: # encoded_image = base64.b64encode(f.read()) # n = 79 - 4 - 2 # for i in range(0, len(encoded_image), n): # print(' "' + encoded_image[i:i+n].decode('ascii') + '"') ICON_DRAG_AND_DROP = ( "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" "/8mFbafV794AAAAABJRU5ErkJggg==") def load_pixbuf(encoded): loader = GdkPixbuf.PixbufLoader.new_with_type("png") loader.write(base64.b64decode(encoded)) loader.close() return loader.get_pixbuf() def load_cairo_surface(encoded): buf = BytesIO() buf.write(base64.b64decode(encoded)) buf.seek(0) surface = cairo.ImageSurface.create_from_png(buf) buf.close() return surface photocollage-1.4.3/photocollage/__init__.py0000664000175000017500000000165512716673056021544 0ustar adrienadrien00000000000000# -*- coding: utf-8 -*- # Copyright (C) 2013 Adrien Vergé # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. APP_NAME = "photocollage" APP_VERSION = "1.4.3" __author__ = "Adrien Vergé" __copyright__ = "Copyright 2013, Adrien Vergé" __license__ = "GPLv2+" __version__ = APP_VERSION photocollage-1.4.3/data/0000775000175000017500000000000012716673066015656 5ustar adrienadrien00000000000000photocollage-1.4.3/data/photocollage.desktop0000664000175000017500000000137412716673056021735 0ustar adrienadrien00000000000000[Desktop Entry] Type=Application Exec=photocollage Icon=photocollage Categories=Graphics;2DGraphics;RasterGraphics;GTK; Terminal=false Name=PhotoCollage Keywords=photo;collage;image; Comment=Graphical tool to make photo collage posters from multiple images Name[de]=PhotoCollage Keywords[de]=Fotos;Bilder;Poster;Plakate;Zusammenstellung;Collage,Photo;Image; Comment[de]=Ein grafisches Werkzeug zum Erstellen von Fotocollage-Postern aus mehreren Bildern Name[fr]=PhotoCollage Keywords[fr]=photo;collage;image; Comment[fr]=Outil graphique pour fabriquer des posters en collant plusieurs photos Name[it]=PhotoCollage Keywords[it]=foto;collage;immagine;fotografia;fotografie;immagini;poster; Comment[it]=Utilità grafica per creare poster da collage di fotografie photocollage-1.4.3/data/photocollage.appdata.xml0000664000175000017500000001164212716673056022474 0ustar adrienadrien00000000000000 photocollage.desktop CC-BY-3.0 GPL-2.0+ PhotoCollage Graphical tool to make photo collage posters Grafický nástroj pro vytváření koláží z fotografií Grafisches Werkzeug zum erstellen von Foto-Collagen Poster Outil graphique pour fabriquer des posters en collant plusieurs photos Strumento grafico per creare collage di fotografie

PhotoCollage allows you to create photo collage posters. It assembles the input photographs it is given to generate a big poster. Photos are automatically arranged to fill the whole poster, then you can change the final layout, dimensions, border or swap photos in the generated grid. Eventually the final poster image can be saved in any size.

The algorithm generates random layouts that place photos while taking advantage of all free space. It tries to fill all space while keeping each photo as large as possible.

PhotoCollage does more or less the same as many commercial websites do, but for free and with open-source code.

PhotoCollage umožnuje vytvářet koláž z obrázků. Z vybraných fotografií je vygenerována velká pohlednice. Fotografie jsou automaticky velikostně upraveny, tak aby zaplnily celou pohlednici. Výsledné uspořádání, velikost, rámeček nebo umístění fotografií lze změnit. Výslednou pohlednici lze uložit libovolné velikosti.

Algoritmus generuje náhodné rozložení, fotografie jsou rozprostřeny přes veškeré volné místo. Program se snaží umístit fotografie v co největším formátu při zaplnění veškerého volného místa.

PhotoCollage dělá více méně to samé co mnoho komerčních webových portálů, ale PhotoCollage je zdarma a open-source.

PhotoCollage erlaubt Ihnen das Erstellen von Foto-Collage Postern. Ausgewählte Fotos werden automatisch auf einer rechteckigen Posterfläche arrangiert. Es ist möglich, Anordnung sowie Abmessungen der Fotos und des Rahmens zu bearbeiten und Fotos auszutauschen. Das fertige Poster kann in beliebiger Größe gespeichert werden.

Das zugrundeligende Rechenverfahren erzeugt die Anordnung bei größtmöglicher Ausnutzung der Posterfläche in der Weise, dass jedes Foto so groß wie möglich dargestellt wird.

Der Funktionsumfang von PhotoCollage entspricht etwa dem vieler kommerzieller Webseiten. Jedoch ist es kostenlos und der Quellcode unter einer freien Lizenz erhältlich.

PhotoCollage permet de créer des posters de photo-collages. Il assemble les photos qu'on lui donne pour générer un grand poster. Les photos sont automatiquement arrangées pour remplir le poster entièrement ; vous pouvez ensuite changer la structure finale, les dimensions, les bordures ou intervertir des photos dans le maillage généré. Finalement le poster final peut être sauvegardé en n'importe quelle résolution.

L'algorithme place les photos dans un maillage généré aléatoirement, en remplissant l'espace libre. Il essaie de ne laisser aucun trou, tout en gardant chaque photo aussi grande que possible.

PhotoCollage fait plus ou moins ce que beaucoup de sites web commerciaux font, mais gratuitement et avec du code open-source.

PhotoCollage permette di creare poster da collage di fotografie. Assembla le fotografie che vengono inserite per generare un poster. Le immagini vengono posizionate automaticamente per riempire interamente il poster; in seguito è possibile modificare la struttura finale, le dimensioni, i bordi o scambiare le fotografie nella griglia generata. Il risultato finale può essere salvato in qualsiasi dimensione.

L’algoritmo posiziona le immagini in una griglia generata casualmente, riempiendo lo spazio libero. Cerca di non lasciare alcun buco, mantenendo ogni fotografia la più grande possibile.

PhotoCollage fa più o meno quello che fanno molti siti web commerciali, ma gratuitamente e con codice open source.

https://github.com/adrienverge/PhotoCollage/raw/v1.4.0/screenshots/photocollage-1.4-preview.png https://github.com/adrienverge/PhotoCollage adrienverge_at_gmail.com
photocollage-1.4.3/data/icons/0000775000175000017500000000000012716673066016771 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/0000775000175000017500000000000012716673066020430 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/scalable/0000775000175000017500000000000012716673066022176 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/scalable/apps/0000775000175000017500000000000012716673066023141 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/scalable/apps/photocollage.svg0000664000175000017500000120152612716673056026350 0ustar adrienadrien00000000000000 image/svg+xml photocollage-1.4.3/data/icons/hicolor/64x64/0000775000175000017500000000000012716673066021223 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/64x64/apps/0000775000175000017500000000000012716673066022166 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/64x64/apps/photocollage.png0000664000175000017500000001251012716673056025352 0ustar adrienadrien00000000000000PNG  IHDR@@iqgAMA asRGB cHRMz&u0`:pQ<bKGD pHYs B(xIDATx͛ieUoez6{q !`'B!CK(@P&$ rLg8;3=KLw~T~߭ϿNsE>ŋ+޵|g{DDmy@@@W;r9}n{ͷ. { B Oi}\;TY<';?=_ 6YɱChˬjˬ<<5i_wס'sk8*(|O6k-Z'q?S}0DZHZ{Qp})dнXh? G?잽DkX٫rN֥X;vFhyUGfiA0:J)KYG0:{X;ADQHddY7Ʃ<7G>3rIL ̆HJ}pK;?ƕ1`WEhahA:z+Eh Q9D+IFMq(o~绑7,ח,/_ |<|3GY>24֨; 0\@U{f`$Xt3SV"$#N-F %Q\kfrZDؿtX$6;vbҊkJǎ-¹z @N.7H]Zc>xƨC0:ڠ@Kz={עbZ|j9\7F"q &jW(TۜsZ)")DBh& &XSV>Ʌ'(D!zf9{nLXo2Wq^;"y>ED)2J0[)E\\D4h4;~9>t_ys-W06yG&Z@ fB1 ~w=\*6m ~MjaFȒ3!"hB<t3z+Ɣ{kj"k~(YCwuՐa.J(F!{N.f`W.W`!^FGk݌z$FvMFJpuB|wTk4>xٌJ{F8jPd8`CpoZ @·o<>J'sX"y:̒d t<&7z^;ɆNd;Z̫Þps9"pғf~+eэ-ǔE6ċ‹| "'?o}T P+wD"Tz^cJ=`@/`p enló 0SU"?~WӘ ݻ & Z+DW Q,Nd}Zt,R ָ&Uۺ(:QP*5޲$#I\=_a\!uvʵV)cbLQ.(iw|ѤfV){h4ht&Af#Wx3! EHk`PxWe@ﳵgb3v ͸~ߵͬ< "JJp()@ *e7zUb5Y意axB䒂^=` tGDaD.ĵRT]IBPH0( u8\ǟzq9~8h8SwdM[9PC8K #n< N@LJD{mD"Fowxq?{V͇,|GCI'a=`G|Xf;C 5yD+R§a'y,#_:x /Hk;GGO:R'S; 횥2%hBtogDP*/=iq& hǠ8f337dyI-M{^9 'O39@cNd&Iijev͖)b`(b`L9K} ^!"Xk./kiO"ÅL.aC y9dyg?xRWq0W ]-P % \["Ǽ9VrM\oZƔCءj>Dk{6<0l; 0 9xF} tт`~G)Tͼ,/P*WzEn94ɖ!ۨlP&}qZsH++gcgz3`ΫQ s"J{D<^!BWoR8rGDa%?U!,ɡJ38,TJl5gZLeȎ={fx^v$OΑdI~SRXQ(욫0?WCDX[oqqA١ ev8y0!4BzjeVε){s$˗єL1_Fb'6$s +JH|f<6MFp ĉTb Tr'F ZSAˍ| &ŠdC{~>y6dAϽÿ ={iڞ^yS=Mims  (bK3# 8h3#c;AP=ex仑V߹)anYw󃛶̱D ?q}%MNx"y`d`sQT#fq_P 7hW==ĉJB8SoĕV'G)ʗ2KEV_=%tEXtdate:create2014-03-05T13:56:33-05:00Ȝ%tEXtdate:modify2014-03-05T13:56:33-05:00p tEXtSoftwarewww.inkscape.org<IENDB`photocollage-1.4.3/data/icons/hicolor/48x48/0000775000175000017500000000000012716673066021227 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/48x48/apps/0000775000175000017500000000000012716673066022172 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/48x48/apps/photocollage.png0000664000175000017500000000724212716673056025364 0ustar adrienadrien00000000000000PNG  IHDR00WgAMA asRGB cHRMz&u0`:pQ<bKGD pHYs B(x rIDATh޽YdY9ګzݎg8cƱc0 , R@(/$(R$b JQ28^f=TWuUnn.|m;%q<{0Tg#=1Ѕ8|'J\|{ 1;_hlux}c8qb<=9<e7}b}xSόp>DWVɱSC ЭV/l<@s_J~<_cS;f] s,-ݠwu8718HZ 5OkF 6͞W|s-+oJ!%+<ݩ\׮dfqh2}?$Jw JzUVEj :KPQ#tHJ l.Pw dsCiVJKb֖Imd([AB r_uЂN]>q#C yo!J(h%N3a9l4eXIӀ' Tcx"Tm ҷ0&[ SeD~gchZ9φۉtXɃh@Na ߯qB>oB9by3}Ch3s(YpZm<P&x ˄!Iī IWx8A ;qe!ZW ~'0!Bjrq#U|M;/ eDLׇCYŦ!" t Cp2D?0]vyxId6wD&(9ҏ93s?d5ODlԷ)5^ޕ"SgBo -=с|oBCQ4noSYTj-o. Nä_!?B^`CKBDLf"Z JA.g(4n13k&rv6"XV_En^̽+_,siN'Z,myւV%U"f Hv"ͱ`:\ <_9˵hCW-_ezp-Z7:3]c`BY=  #a,Q(v[7VElR(A9GbOr3/3lƊv*}v=xjZ(Vܕ#p*rBG1F+f$lq#4gtveEB3Q 'H1aAW "8ps9֛!)&:xj:ʙ,m$h:EbC||/F>w>_|}#<6\a+<Ԯbn^AfxW%x '1Ge< ْ9knVׁ]^-@O{<94jRX=>oRu~qE˷VyG);C3@V'faX!h)F6IBƴ bwo(2G|_xQJi^5ə'< x `(R\ϙ\(56+ Gȕ~(f~y*5*%6Qh:3E&2.S>AahM _iPo"jN)JTK) fǑKۦZ,=y$խKTrEln?|*26sk5('W 8 BAlx1&=8ǧ>s,.mZU})K((5hŝ;:p |-HJIL|Pګ#ˍ Kj;aJ:9ƑG"Ǝ(J**rZ뎅)5uLZh 3BdL )6bZl1e:z@nT9R'ʀ%jr62LTy`rL0r\[sÉ]&$!hl#Zgy@)<&Ԫe[ 4 0UO$Yx5Se'cSB@!/Ls;ڊ K &!AcC-֊CX!$d%tj%/ '&kBfs F!ӕ'(jcpϵk[Q+Pqkմ]Mf{qd)al`~ЅvҞ <ݢ31fQ3.}Sb/%t0-&2ؗv Q1קEWz{EQz9[NDOɠT`%tEXtdate:create2014-03-05T13:56:33-05:00Ȝ%tEXtdate:modify2014-03-05T13:56:33-05:00p tEXtSoftwarewww.inkscape.org<IENDB`photocollage-1.4.3/data/icons/hicolor/32x32/0000775000175000017500000000000012716673066021211 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/32x32/apps/0000775000175000017500000000000012716673066022154 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/32x32/apps/photocollage.png0000664000175000017500000000431112716673056025340 0ustar adrienadrien00000000000000PNG  IHDR szzgAMA asRGB cHRMz&u0`:pQ<bKGD pHYs B(xIDATXí[dUϥ骾Ow3= ˓hhP/j'_}P^QHM2&(̀ Cߪ.ꜳt]^]O׿GG8wv3ӷt|"N /57U~9_ 7px|ؤQ5zzF<N N9S<|>;]LPgU^[&[p,xF1"0́B#utfgk,ibyfrgqR[2g/LDP-.r-gS&FAƷv$9մF,kmj0(afrXmH n) WFap_Ь>qx14-lI V)tO@ϸ|qǽ'( PHYgY fA A|DB:yb Au`Rgu@8ebvgs<$f/#|Om= *DfRD6 N vPqc7Q_Y&;,e}$9)*`G+pbnz`@z)p*%]UA䕧Yz$[w2\h !:%R !_o1Ӣ Ta?`\%G"ϐ: n{An;r#%s9~q UCz%vi<܎̭# =J\2Wl82\B%c-_M 9k K]:v<'vPo5D~H;(>k5I'@|o q@mADx8|#O3NWЯWbumN]R! <2HtS$>3p`t(V'=pkcn6y ]dhlKwnF 1w8aߺ 0j?=~DCѶ:e9͈vOEN2,N Vsɩ =p'F ANgu<ژ'V4~^eqhB m( *eK$& RP)qW9DmG00V[ (dPF#el8bb,(du&;1EE\`p.R[T*}fTnJ$Zf&mxx*|.zS qo+OrRHAUj59w:B^o Ӿ p&Ô&^10 3;3Cad)&آB4c7v?)ʖ`ת2?YY@}sksa(!fM[mS]Uh,mv{Ty@ oX~0}U!m48*Di692)xFKa[+\nɻv`$H8o\`!/:Nr5%.I!JuW}֒9%ю]S@@ɴ 11"F*Z<ԄoTR@J\'+ XN4`ah'7Qq,n M 4$_-O^? #,3\;k֢:֪ i:뺡^]x,ڍHAпUj(v6w; |]x,KC(@< XM[%2VFwpB#?)c!mq(mu(o\|"?m_??t0*8 2z8NLXEh wT;H4#>v,B)͈oIAHҠ*T7l\[fk|O\??`faYxx^Cু|;vڷ>c@X͂>0 ս}Fc܌-@)V ED!DuEue._g酋LYe:jҍ>|aP y1 7p$RWDbj(\ImaTFD֒0յ Օ56.zfsz^2q7vf<)-l< zidqS~BjY݋nw e!zuiۃ襀(UWy8̑nцYh&B UNL0 Y8;gX%PU;1Zl" l ba[J~!Mzh#KC.Hilԃ m;N%B%Oi<̀v[Rf+6hQg}z%p@v C!Vׄ^Ӷ"w➑*`@ Vr/BpjGfz>C( U%#NNX:b/΃hzm?  f CxW4zbWn'{uSysS*'8>3 ŶpʭK0Afgr `8c\^YV5(‘U\jP5+(׷4a2[8$`7'`^)vD F ؑ{h؃_&@5xy*蔧E*Z8l+F=GUAGh՗8]uSiXJ0hLў[0M#`i= *.nVAۙf79?{[p3ȳCU)N"nniv\oZcR:7d4C{0ڣ2뱽²$V&P!i:E4/o UL5yvy{g lx(6vf܅eva9XvE>X.ǁO'W4 ea w U:dt1|yKkOPY΂j^Fʭ(Eu"&7975TS}fM4~1ـ} 1ؖKt1 /CB Sm[ QUx=_# )4/opjl\)Jf ˈFLhh`, ۖX+L(Z#U@ݷi 8i Bq47񸾹DŽ~n}DG "v V2(MDE%[*Da>Q6q *3zZ0Ԝ̍(uH= pN{t=?F w>P;KKS BkB6{(ˁ0~p,UB8@װPAš *ge+E:(E΄D RGh ]nacз)%-ykW6K0" "bfFUifR~}-'/: (tb *q[H66Q}́it>1tHn*p =^ш׷7yaUeuFc*z`KI+"rjAHz)c4rGt"mR@)Y fd~?-%;n/teY+|ifchVcnwvZ\4z}s@i8y, ^x ٬5%`0'`'E98la$&5ضރBFc珟ffZh(W%KѴG3fZyX֊e cǑV) 2̐˻\x 0eJ_o#c$ո=B[]8gPVsţ~31Mr20CmUJ u&ԫ`9%f&cs)^xj(F9Mkݤio;[,+>/p5[[׍ /r5k+B Mrţ&%'-R#4x%\=嚬AYu'zm)3DPS IahZipFDʃP۱):Nh*[B?w-pH`mNH)t__;K . ֨VXwߏ6ZE9dw0hí `8 2öEfG~g?E~b'Nμ}C1$7#WW(: ;h?U, F5l+ {GVj7q 1 4Ä&yx7zy|=bma€7~Ʊ$N`K<2e6_o&?>UC~)m͍u]W`9%,;gFgPHeb4N!<*CC]j&":DE>;Fv3 ~9KE^R 3@t+y`>4e 3u_~B ƻlg`p `4Wf,߹@!mllaV+W:'j>i݉8IF_6ҸF:;'ڀ{i!DnUW[\0؟5co:!S{h% ԫ2sן}RO8*2֣jbFZKI+rhPa>z=F:El{ '?[[̅q2S_m =«.:kzNjҹFJ/'`b]zismrW't/ݫǸz KڼOrk߲/eGQ(F)ղ0Fk&~mL#R@LU40+6eDGGBZ_"bGJp|]KI~ DBNMIV*gs )fut߰Xb Bk*h={WڕSIz[SR($\|ؖY1jwoBN $ ](oosMZ[}|bǏ BA );KjirF$mrIPÎekTϰ1BƹL$k6*L*H0nUjZX} }[Cj- C Ϻu)0vKĘʪDs-hq8{t,BE>QJ\IV7O If aNK|.4ەVm]+1UYiw9=H }UVHϹ4Z=&z"Î|_Ž若9fB Tc>~ PoSlbޢZK䈭MrZAgcE2cN4iyFChB3 q=fgÎ[W,v%G20@;RZ/-v]'/oeho\c5,3CWA_IV׹l-k2TyZBjcZDwH-Z6(F6n$hk:;Lf$h̥ Oֻ?.dR|>$怩㗄?Q[  ѡ6Bt!>J? y: u{dY@rk ( o.nK8=)@JѸn!!0рV3M;JNh}{]BӁPCSu{ϭBCO9g;\{D9G4;AĵqKfJB` 8Fndʥf{@Kϳ$4#M;a;İc&{ARhpR[̮Dv"E+!(\ ?o_'`jZ[ɻĎMV"qLaF%x D#|!!- "d1XRW<\fsc?o~+7]B&!DЭ/$@pH7=>*ZK.BʶtmwVI!o_ISh%vsqx )֔UMm|?DafԼ9g?%F=ÒAlKb!O-^KRǓD)bH!`jns7+3KlOݼVw@̚JG|ÿůC|uW,7y3p;zI%G];~;j" ;IR!b[MI-gv,]f@o@ ]-J17}1c}sbI{-0!RBiV>x_,|3뀊@#kU!^.LQxsżQIGU RJhg~ry5E`lxaOu6A>ӭ15\eI^'HyxσB^H@HwŽ)@q:O_7^"^5)ǩ>H^њ3\5#>ʺM!Iq0$C?j@vHJQ? IDATOYLճӍ:Cɨ( #A|/IgrI4jFm>tcSW_\D&Q'CP?O}O~䅽\^O_ẅK,Xt.1ܵ6!D4vZŧ^ @Gx:Q4y`0H> C!:nڏ/}\Xǚ_uB-$MqTdI@YH3I@Bi&Y6 h6hXKہ !v,b_,bb4ՊǍ+Խ AH!5\'qDorwԇCE{[@4TRA DFafa矼PͫD 9ZeN26V2سp FՄk?-5l9{ɉpP*hhbƵ56*loVhF"t68r[+(8ْ;*^f;B#]|xgߺ~sNIl.jr}>fh6B@yFe <7OyZ*QA(uBN; {\=EG~Ëĭt^35쪓Tky(6@29@oKoa 3?=3slWVˬ hHb$$JZ4|{ ]ccd $%@C-=>| Sv" xb`Fk-Bţ8D ō:u0^rYIj^z"PHeX8 Rbާ%$ +r\r!{%d c z؟`ngt~|TνnǍ0Ӂ;pH*b?k`UyAl192USBnWY#ZB^ui@lxUe[W,soQK|Nɡ /^+xhFL{+ |zI 8L[ :@tFe$@N\qOTaYdDSh8۵rm(-N80(^I QϐkEИED>6F@d5!g ʹxY,T!-So8_ ?b*ۍrӂbKю4- юU;h~uH70AY7'4NK`iע|x}zlFx.Yl#ɨ m9 44y3XJM)d&]p~27WV(# P0>͞n~Wy$OVR;BwriRŊv$3lgRKl 83&WV&2J/nz|s&]/~K72%0=fj>WqskFĠUPp'B ˆ TOoq\N1(&לGˀ){=J?_gG,2KY0ft{h)sPbԏ2!0C$Z iQߥ4GjP~TtF*I@$`%-[~%7RI{1J`1SRͦ`irQײoqnX@%\jHഭ*xyۘ> 8GЗRcy;]$ɇh|HN6 Qj`&5_ֲE0#fJ‰ &JEc7_\r8BFXB# W%ߦGq7rݏpfh8CKu̒Ñ\s)g g<h gHKKǏ$vhʆ60dmT-r-^ya+@cq1$)+Ko$*غg&35:g%,ŖWʚ[ڲ#Ep2qEA*o,䱭lV7458;n"7k5ͦ>7s ;a '`|Ў?8NiЍD 0a'VqR7ڐ6߇JK˜>>TB!\h)My!zHSgܹ7HˬkK!v5W67 c`&fv bak^PO; VL^t,PWƃ^8,SG21k5HH|fY3+ʪWC֊U %8Fl'\ Jn=H!% %H#Zo=&P$a> !(# Y$¯D' H)FX ,& "qtriP,#MH)"eYl*E9TT"E%".nYX8%7Ǚ!7 6NVb 8D {u[ hH hXѱo d$FiJwە[Jpn~.1Yc`#ˑI<ÉB>SEgo0Q"I$RZ(PʼV1KB7i:0Di͹QrFZX(E޶[r6WkQ uQ ȮO~D\ݰl A'Si(WkRJ.nqᥫXBpv~SS.]Œ#333_Br [^4`9,nL\_/s O|G_M Dy>=wx@0EхK,nWߝYazfPQ y#wBa#C՘G%'`+wX"h1ԛG*Fx_vusSh2?R`U? SK_x}ԂzR#WcqTEEc|bzya&z#S/{O#S}G4HQ#ǒ]cV?pk[F-)9w$Tb)z[[ʴ[\~a`$6k`LzIm.DKS vTQ9c[6cfq߱)|s+x~WƏ49ĸ8WG`s졢˒ܬ>ğ]x_ZX*dugR|צz~_^[F0 _aT#J(=)"A\߇ 9 v]M1 d!( A9!4y*;ՠ_<ί}x33S9*D{frg{08%g.x$֔C${ϫsKWͧ.t!aA6 DlΪ3sTH)P-_Ȁ7FҰg'=351t{kfbiTrƥ`ʏ𽐵ljcG(_T4O'5)\195LODG|ia><6IVqGQ :ҐP7i|hs U'`} ~ۣ ݱ)j^@󹶲vss%枅i, .-Q |YuP^kˋcG',ל獯:cYͅR1e:㋗Wx5ַ,/j, 3 WR:H^8'`KIct۞ ?.l`[/-8Xn,5w㮒\ $[U˒\O'+0V, "Rbrbɹq\$. xsw_FRR]R @c3hGṈF2TPv{nt x?XPҩBFO٘`얍I0sml¶$("%A(Bq%,nlģOsvȹ>*} o.,FJA- )KWkaC7\|>E& wsuF"lVY ɜTȄ,nTXެYY1[5?ROqmc#WrW]g).)ɺ' ӎ;,z. ӈ1C'3.dshxy-.O!vȹ\ǢD[>/379Ta.=RX:s;_??oXxʙs $_llVDc[pm?^ ){>A B^X2#e=u.LqnaIg"Q "xy?ISc9LíЩoㄬ0Į7r@V[k1h! z{Yb\H@dꯙk a~1dhżX1o\!c3_>WÏA!\uG7oSWKkeNND*sfXmKƋy2SB|G JEBb&礏nBb۶96!qR!O КS,UkKy^}qp"c|76+x.γYyi923D)ZL9&*$6M,@SBAS@}qRX.k=xqIAts\TzXHM P0wt'2:o4L;h5غ9[DR~ǣ=:~E`ɦ o ĊBa0Vp;VCט݁:X,tssm,)x_%\[擏> Z3+X”S/s"o> ~g̕;rEYPiX "n,JN3ݢ~}/ƞ{l\kUvB0{ջhoas$%(Tgc#,Ei#^# 9bű-sqk[,f%2KSH '+$VsU⥫7s ÈϿ &ezK|~kNkҰ9M9&9T_!>W@!1].O,m[!HCG3`LPa4Wx^Ŧ85m| 0@Hk' A&7\t(^ lj6VZRM,s?up9 ³e?-G?m?? 3s&'(ކSCi.?DʻlG!eQ Pp,zb{grF;lV W&E{yFn,nWѶuld~;ob{:_fezg(֑3OMйwB -?p[BY'y.]GSX5l^,t 7md IDATU۝8 C[ u&D 5hn;xJia[.w;TB ryljjN8.RlMgckie Ils R+HLDϽm K`Y2) 5X~~ g0rlT7vTcBR/MAUv02(*VzvԷwxJSˌ'߻ؖP1_QAȖ\oWibsq!+}Hj&̧R`g88RAq|Pkl)cvVѠYԏ~;"P{ bqv9_eѭSR0s΁FTRA-Ӓ@iR')ym& #0}–"m|6aP*b.TsٰnSzh{ Oij @H9beRz>U/&GAXEC-iůģ-c!`I0YBDnEB-L*A!=&]Mvܭ>00uÏ'`'> \7c*>ϋJ6"19 Ʈ7 B3>ҮqFNH!˨߉~zv_q~~}kK]wm#m{g>BJI h]"SGz3Ikzf&+WT#גߟsN\%>;PJYnl 4sZwҝE37WptY_?@?&+I;ۋo,YøkHA [;a˄ݏؗ("{_ص_i]Hi' u^h1*>zwsp}6`7M l0q$%K| #mi~vFw'uw 8 Hà밍f}weP ^FC]o`O+0qQN׾j4h٣rK]|+oV`ss59^IBsJ._1d;@')`/BI/iH';zL5ήC)iiF!to?}{`>8"X)0ZB~~k ``]#p\b/y`A!z =>w͒D{FaT] utB6LK{=`4xB;GGӼYqY\jH K$oq& Md~т=CM>\->CSAln_-6y~srD˻%0-V[12MEX,]^N[u~_ '97;?U׻ys3`(A2%Z(Y!]9!:dk]˖XyWMKJ2ѢLIPK>fsϻ>*sȪwLtuUfVVvWwKtcj9jo@%R͌` /^2%οr}h痰 vFs=9) `M촣5+.n+k_ǟ=D LI6H@V;t0G8 vߵgך+l6.Sk^כy>N_B"EHCA=v {9K:;l]=})R*ܩs}o /J)67|rʟqr v2Rxȥ2YxDbARtłı-phxEI܌8!9w̪߇.gk G@a'O9}e}C=ل"/?g{Idu6Qvk|!™P]CP^CX>'}/7=Eeu{4V/pu-ڛl/St$Xk|KRrmMKGk6[$b25/@!tFGNiO SsF\Ƀ?`E>l~y)U7{31M T E mYTS" +5SlǶ(86 *Kk\d|ܦR,pz"Q{)\D)3D )aѴRv ,^^a:h"RonIbeq.Z@f PN/K2i[j/ ë$=l6hHa\ PԚ͎P¬uJDsgIA`St@ oض[c8ti!@JbNʁ#LO^feH٢ZZ>K~v5ŕ+M$OHbf[/Dd!?@'OxjPXeF 9s ˖=VZ}|}P|42BVlފuB2J]?J"˩?d)`iEcSP)ٔc%ЈԛUMz#oswXZ]aeYs4ibuRMltf<} " VW|nqiGs%iӝZ鞯<>VPKLf8Z' y`$~M=NK+&T"j\Zlk aI;X\uPlVM̔B)ϳR-^>{?[5ʥCEs!Ս?n֪A,Fe|f KY,lUuf1> 噌iyM4jtpd `Nw.c7]#~;WPpB-*3?m^Un(͘ nH&*P*)u-uVjgvڦV_òLϵ:S%ϏQZTQsqXhx~:wd]=t8XQh4%AH& 3zJfo"o,S`vE545wEiܖ)J@H[)V$-5+ 1Jh A}G[ٷ(;Lm\_+ - ǒׅ+.ooG&8+5Vl DeqŹsU,T8zcKf&t)$\#pyJy׮HV֪@i ZMelmblH ZHP,aF|;P˥DhDF2"gD"]:o}tוi=F<;久 Ah:<@2 & SQx.F+d!FhhhM}SŊD$}LaH ƦyK/2?^Ʋ|j5ASLMH*ezӥ`;LNhZMeël^:]e|_srG-i2-DЬ+[to4rFH}+O`QjVߵо-RBB&@!MEo6?ug|M>gt|)&.Z*(12MNtýQH 7}(O{CX>x>6usR)C'')%7)tWԛ>BN?ſE>Zbi\׭#+MN/$/̔XpÙ76i6bpz}3Ew_\cbfcɓ<_3VXYo17]d}E#x¶AJA@K "B AJ@? nLH t[S=b\+~>w^/>YfZ3֪eaz ΢,$<Ѫy0FBH#8!`y-(:̴ͱ&&,lK0;]uBf*.pm]dTlmrt~+7h>O33W`iQ39aqT5*XA`{B54hEJErYZt﮲QX$z|zb[O,؏տ!}zLӎ%ɷahl'MROXLM8lIx-ZlX/P),6(XS9Kg.o?1M5soq-nT@)i4\&XXlmZ N,\|+e5C$K:'6h5KU%MieUv泱sr\8^"3E>^Ae~_>G4`Q#K~d*v,B1D0%(5J~^㋟[K_s=?9;50; 1z- 8]`pU0t i bb̡P j hlN0VYX^m2=YZwi|.-6xzE ޼zO|qO3WW9qbY6M)e80ZXa~f?-2ljUƆW\Zf|\rP_?"?Y4 Vl ofå\rIrpwQpJ:<*oLX]mqi*%X8Nۑn((^L̥wHCƠF`gJ1ːpP'`oa|1ׁb#oorpFgyc\RBTgbҵ*R ^<5[hϟdW[\^^7q Edee91¾2i4 KժV6YZj:36ӓK E8$i6|>ߢ^=2*|XZyS>Z~ű#̶CZf$+_Ղz㓿z=zrk50ulᶗ (hF u2 RBp֚q'_Xdr-7 %`vipZ/%Z5JMP-J;rRJFƩ5ɱ&-oi[ _qCl'O+*ٿ77u=p]B7*%V O\I#s 34\q>9&|D_\og;ō>O̐g"+qY%AQ{Y~a;YUo 6yFB5ө6B@Jó\pXyuopk++G(J<<瘟)bY.J7|דZbeec5JqNQa~Nb٭8Ak-ZqHfir, 24MNAbgjd83O5OR [ ٥ķD0k>oSFS{4\ S[;mxNm8='f@hh/6Y\^, p"ҋ7xUk87ց%\Ht?ީ4q^U˒2=C_`c:-s$A* {8M:W׷A嵵 ?ȐRrq`r # Mřx& g?;Cd׍k:_euu,U|Uׂz76):tJyY/@=t- zR$a+u ޮb&02nYq&12H4}8˛5x֍6!B5syTJ6#*Nz XUkJ|WUter=L >k×.ոtQB[u#vc X@}~&<{`z2㍾lDz}m"SюН[Cx4~1B>7^[I⣾~'E79E86{; iSmU95!a v_׍A4<~r3g29|h@']P/=[._>'嗉̍ΖJtkDJsMʰP˔ډ+˜_XQ_ { 4yD8RJ>(bOzau_qdq?`tW8 4ݏb[6Z{mϘ|X]LGC|xex#BK?1<5iV,2:iGƏpPʘeu FH`0ŐWEU#qALmI Ķ$4s=\jL&NQN45j*3|N3ffETrqScKtJlU7PZ!iJ{lUt53?q7bSpj1@GK柱k+~X0?+Aab!x#DZK!T!~0 (27%с!k'ޕoS`$_s }/t~yq-Gml poX^g/]]3g\o[ݸpuwy@qؐ|Oa#XZ&MĘD+7) sFL9Yk ސ.yǣB#Ajڲ`<׾'J}7=s9KĄztd}}6;HѷԴ'ҼiӚ3gN+\|ǽ6'<Ϲ~vhXU_BbƲi)!EA1 2A.As1oi~4++sS ++[i~F~M *v~@!22`ሦ0aO{J˛ N@>PJAiidQ1;j  6=6?Ube~'@3v]Wlh6AO׊kbl,Xw?1}E 3d'OG@i0VWl%h[#IoҦ4Ѵin6O;-M%UE%ﯾ>?$2WV|S01rܼM ){[;F]1QBR;;춠 !bs\(A"$&f)'at|8ף5tւV˼}[Ef) S뿩9x>!XZ1L3R` 0V1Wg߄/BWVșΑl.t$Ɂ4B9v )Fd`m~iDl:j4O%sw9ڵcz ^4 !dMeV+qTR\o6@g_ݬ`#-'.2mO@R3k tki>[zK0azhm?":q=?cA!#v_iہu_ Tz*#wk;vb.d(2Z)]@`7t!7I)D\ 9NwKhEm xs-z>u#]^-O>Tf?lu䳉ɿ^E =p7XhQ_&H !]PӾCqT#N96F=(t#M`olT͑ROkCxI*GI{a@kXt:&KRN5}eD ;$@Һ}n/5/ H`[!ȍT' `vۇ@?z(mE[C݂;/zkS>y _2BrABMGkrqN  nU68X[e7 ~x b |O :g:ֱڄfǡ|q7X6)ptu$o'Өb44[rоmń!K;H7 ~yRH 'u޻AI/?A (VPD偯@$ |?Iܝp$/%Â-˚Rsc;Z(Y#Ei ]#$ Ÿ$;S@DI!ǵh@@T~^iA7=(Ez\V\r嚎#q'pQ&r  iNU } ɜG,cnH \ 5 ab@ RlX,:b[ׄ:@eJoF3GM/~N S>dyٓ4eMЁy#9ET2Ibrbw1rN-q,xhVBYA-ǣ{X*c,-ryz$ oAӖ?i?2D#J"A nffrDPZRsNgr/"0O8:q}#NGiq)fm iL* |ۦO $jqE.^^VL$S,M|h${(Rv 3 {n υ''8?a> y| A:ӑY^ْc1#[CXs'5LIڶ|j68ll*KQ* !ɋ"%y"G+GhL@ϝ̹?>~8핎~̧][IȐ?Їt<" ;hGx7֖yŧ9KxyY$+d >W{Op(ږz_hRƽFTEBqk# [*W.ū/>ŷ쬤CTF^K0A3z mwA\FiͱÇ{=X(-A W=_h4rp8x+S,q/#dzgrRtq?%r}Ow~嗁/S.Ï~=7]m'6fdt `-o,g_Ztٗu.,ZiTOtvB+q\&v傓MATOQhATr٪n3O3OswHxZ׸=_g¾wZbY¬7N˓s'/1IϢު@piLウcY(ixtgU4\tjWr$[RrXXK|/XZR0&o¬Vc f-xu0 nM Eh<7^LomGi~ rN f OϤ=.&:5it(}C=H{:;3 ʶ%H(נJܠʇfG=˺H#+d/JD*jaAt StdY!r:0<Hӝc ?ϙ Blϕ.Gc^Ph|%;Ҩyl,%k 5)L@^lGXOw"}*::=$fdj'2=IdB\*lQ/~HEf\xM>ZTKRPl[W--_Kヌ1(PHq6;4f1e+--3dBt߮'PO/؂g̨1RLiLrn ٍ u!>ᬻI+b%=+ ؉΋|X5))IM 2=%qxZ)i_MEVt$$|xNBait0-+LO fյi\7[]ZP $mFBRo #AYXvo)sL6)V2!" Q+O6DDn!s-Iq?O7, #Alۙ ʒefvvmʂ uYTܔh^ˤH'^b$LA_Z2>%QF5.PN $ /X?i6 "I|8t85g, {.i#($, M Dr;eX1e*vDͷ"lnR03Ѿ,+X]BS&ZuӱJܢ) `@98VBfެ#(/4Z5Xv"[1$V0fFkҀuPkG*y|τU&EhT;\e&3G@bP² aY4;kχsG{Q 9 2ӲƞO- 3mQRЉϜyU_@ESB(m"P{:R_!N!+إYbGCZ%˩`'њ ):}dlLWB䁍n.EF 4 t XA2ضIa0=5,u;v/a/oiKFrB `!DEhuid+4 0HF̀BZ:31"&BKyf;/-t^& !vāNm"~(&B˂mʲm <#:]+Y( .&+ -p2P,tr-Oc:\Z*% H Y;$3ly>ei3x#20R m*C*"D0Bvj6 QXAN&g l5L{cׇ5*F#>gB_|feQa@Ee;vrGӔ&5_(9 jX^8jMc*p;<fa|i?3kyI; L=PBjODF{_,`Cl0k#qg4g^}?p&hOE ,@&(tf2BWv Ÿh@ +h//á}-h&dj\/:N:3v+'^^dV:3m6{7|Tٸ9R+ed-f6 }@si,@jWo %Pv@eCtsM&!D!DdLtX\I[/ vH^B73!B+&$07-]#uhۑ6cx,ȕ4[WZR~M6I53PƢGKx B7 퐆-B 8uXV[;2$m:\CkDT$Bߖ/hz~vJ:oze@1MzCWzCS)S$q9 VOyQBg~>AW*PQ.B   'h -IP,œf j*Q()h4RJ`sK[ԚKkH1yFV.5dGaK88+تkP7<(5M#$R=wHp$玺g`Zf Hqh0+G-on `1%3}X\Ycy*bpt~:i lTˏ747w\#@Nҏ>K WbkpZnKBI~}|@$y_BۻcZGAAQb|\DիQչ|: 39tHKo|ZۗoЊIf)}>VjG-̂-V7CӦ7:=O/8Jx^*Aw,(8pz-˷%1p>x9m߱v4(mc@d07̒`:]#|1 \tW-q;8v!PauXYYԛ٪F ?4;)WO<-yf%9?-XZ Fxg'Dcvc^@@`ۂ1Ԅ|Oi"7kj|Y=-J"ڈQ$GAd9p1z&@?kRP)R̀^h !8qjeI&+k\_\sy=PMEZ 9Sk]RQPkycO[w0Vvʹ n0=W,6^ /]WL-8$SSS4R7e6l) :Iq?y;ԙ> f}mJ,y!9  S}ゥU͡}K7 .^ff43!,75SUy⚢2Bh[Pkh{}1^Pb۱; 19&2¿%RwJlA͛TԫGҼ>QHi;C>I9T, `"r=YXTiȞ m"=fVewiQtזK3cewYlT5%o^9q4~_) J3EEz:&@t@gWcFe ̚cjeΦ3E6+2o#= j|Y7Li_ORGm ҧ-b~mSW[ -xTZ9k`i]c'miJ17_/wJ[llifU sO)8kpn1{0y讂r  `P;j;;/aF.m'﷝){s6n?:26l΃to")Lٳ{<ʼwu떼*VDn~BНlWSN 8˷MKϔkt${2Ha;)H@rM'l|؎gސB[N&ܣ1~.F"_^Q]P0yz;NE ۹r+Gv=&4yA('Y*s/CtD}kHG7FӃ{~Fz(ݖ8 $5 DnUlh%%aNGv6~yb^B[Gz3 4EX_Q]&,Ѥ6@Mt!`7IG[٨1WFU,hqIDATjCNց_Q]& EOH ڛz>v`ipΨk̟ [!Y4;}|Uu+#Cd$¶%Rζ^ CU2YALfڶbv^I%44q=Ei4}Po,;sۨ`N#S(H,_`+.KmmIg +~) Tzm2p=Bu[+v@ <]a'OͦlHBKF?B ;) g@Ž74nN5_oT`RP G} |St}ׁ_ 8ı%%ىWHV:j31 DR FʄA O Fxe4-u~jg/wI)(l  2;X2P >Vp 2C {)l&;̾<ӆگ?kłEh#-c+@}菛D7ٸ]e["zFӧe~VgG1xP+u i _ J IENDB`photocollage-1.4.3/data/icons/hicolor/24x24/0000775000175000017500000000000012716673066021213 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/24x24/apps/0000775000175000017500000000000012716673066022156 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/24x24/apps/photocollage.png0000664000175000017500000000303012716673056025337 0ustar adrienadrien00000000000000PNG  IHDRw=gAMA asRGB cHRMz&u0`:pQ<bKGD pHYs B(xIDATH}K]YM4c7ݢ_Bz sg=hZp@Dā!FE;i&it*UscʽUp8k-yN>#_uΤLp`=bamms߾2?DɈLrsuzTEP1tyn;׶[gy$UV{͢5ASU3v8(KQyHP?mZ(A̝_o65FB^(?|mF~0)AU&<0:-]al ΨnG#b}g>s=Z\ʇX7@#Cg#R2BPl nKC z4^OlKT@=dAUc=d@ʍ׿{.sksIj@% ۂp X P@s5- h?1"QN/Xnm7O6\c$B QMBe IQy#K se%uxs(3/ўT:LlQU0FDp-8Ii({T pHuԜyX.24bK)v=asc9EZO8,c U6>rdРx*yヌ3BQllnb%*=4xi?'$+޼rWYsl7g8,pCd ›6'Q-!:dm+'~%=6]l:ex~۪qt!<Ռɳmhkyx'> &UV636ZFꩥ5Dσ߹pD C$*W!]!\{wpCfO^O Q`'2UAIu>,M)"S4'p WIȷo)*g:r_P!ty/=ԗ#mQ3aċt$`3ƎZR'"+Ͽ`^bs \P<"H dQĨ1,79im҃``DA J /,[Aē/;`X1BP*U$ +7òpf>zB YO)r^Bwn4A[ʋ%tEXtdate:create2014-03-05T13:56:33-05:00Ȝ%tEXtdate:modify2014-03-05T13:56:33-05:00p tEXtSoftwarewww.inkscape.org<IENDB`photocollage-1.4.3/data/icons/hicolor/22x22/0000775000175000017500000000000012716673066021207 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/22x22/apps/0000775000175000017500000000000012716673066022152 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/22x22/apps/photocollage.png0000664000175000017500000000256712716673056025351 0ustar adrienadrien00000000000000PNG  IHDRĴl;gAMA asRGB cHRMz&u0`:pQ<bKGD pHYs B(xGIDAT8}[Tu?9g{5dwedVJݐ^詠נ $B@LDQkֽM23;3θWѽ ~m^@𙠸ڋt \#C߽XRX[߃kHE4u.5t'@V8c>nOm@!D^9;#DV4Z(rEvUgX^T p0X6KS PXy+IE{ӿ$ A mljb -h'U#4h:9-Rq`>ooOJɖdx% P828AZK)* ԑpW|Pd]{TvݘVUk:Umu}w ;FU9 40%tEXtdate:create2014-03-05T13:56:33-05:00Ȝ%tEXtdate:modify2014-03-05T13:56:33-05:00p tEXtSoftwarewww.inkscape.org<IENDB`photocollage-1.4.3/data/icons/hicolor/16x16/0000775000175000017500000000000012716673066021215 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/16x16/apps/0000775000175000017500000000000012716673066022160 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/16x16/apps/photocollage.png0000664000175000017500000000265612716673056025356 0ustar adrienadrien00000000000000PNG  IHDR(-SgAMA asRGB cHRMz&u0`:pQ<PLTEMJ= p09 k-8 g+7 c)}6 _'y4 \%v3 X#r2 T!n0 Pj/ Lg. Hc- D_+ A\* =V% =}D66yC9 N@ e*7@9a'5A8^&}4?7[$z3 : 5W"w2 z9 z2T!t1 v7 v1Qp/s8r0Nl. n6m.Kj- g3 i,Hf, b1e+Dc+ ^0c+A`* Y-^)>[( Z';T#_. V%9՜A֜@ל@םA֛A֜AםBwCsE&tE%xDW[[YQBXsLtJqJrL\gJɰOγTЮXP`doЈs~?ϵKϵMêMN[8-f_aZueVy,20.yEj+8?":]5(LNSQcD,n4B!A E"gF5}˚oR:a6{E#zD"I$dM:psT:UxwxZ`kpidC(Yփ|~OdpnWpL JYq#v{ӿuywN,8;>TZJ\`M_c?@ABCDEFGHIJKLMNOPQRSTUVWXUYZ[\]^|kOpr%tEXtdate:create2014-03-05T13:56:33-05:00Ȝ%tEXtdate:modify2014-03-05T13:56:33-05:00p tEXtSoftwarewww.inkscape.org<IENDB`photocollage-1.4.3/data/icons/hicolor/128x128/0000775000175000017500000000000012716673066021365 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/128x128/apps/0000775000175000017500000000000012716673066022330 5ustar adrienadrien00000000000000photocollage-1.4.3/data/icons/hicolor/128x128/apps/photocollage.png0000664000175000017500000003405112716673056025520 0ustar adrienadrien00000000000000PNG  IHDR>agAMA asRGB cHRMz&u0`:pQ<bKGD pHYs B(x6IDATxy-u;Uw̛yq[")ڒHlˊXr,0A8(0,Dil$REQg83͛7o~w륪G}է9u:U%a%4[o@]R"Y"~8/:WZ, < >"/jV~ѕ'>J u ƠÀk<7>7>>yO ǖ{E.4c?SgɥP"x7%R` V6,[߿5"p\&0'֠6>3?h}#lgۋh A,S { w8xo 1>Rpi|RerO劇J{~} ;G<]<(<@ J{zT\*0۩`DeO5m^xy{ 4ZNK|8+#Aȅ}h03n=۝;֖QJM^*K4E cYv)El=F *(44BÑ,4;uR7N뷱2&8Dp#I҃`toK04DaPsf7,$@RN03{6RpKq`Dh@tŷڜo.M0$<`FRܯ*H"s)bGc8&0q;qBtq6Qؤ (n9.FEZ`q(@CH[P橄ӔIuԸ#` GϸƔU}6!K9Ƥam<_eVDqgA]Qt-Ɣ+$L {Ag .ϴr'!ȼ樀QB3\WJvzoQU,yzҧ<, Al@&|Il&>igq6<#,#1 E2l:$TIl `Elǻ", "с30XDYAA E$<%Y3m0aIR*g#j xkI=OQ2?<\" ʠ%\ǹDi.&wUtƷKq=a :GvʙjYsЌ pHNa5A)\X$hDSl6e]xKCiZo16 i[Q(+xĦNTf.{*i u6% 搃)=>R r$Ok^&"IDR&lQ_~+eR eihXne3e [N% U@34#7rkWpCUtPPZflҙzTQ?u!6l\9e+gsu`eT"b)\vjsGF$@}*/~3J4ll| U^DL]xdkL(SxR"*AЦ2MKY7v .D\J4P 0Ϟn 804y?M[~0k=~&IK&=5P82-=1 fdl )FPZn=uɟt`İ@tvٽ"_qjFō|^j ʤ0|Ƴ_]n2OxRQ֊.ſ,W_bg_RQPHR?qg\həd !F6@w5Upr&^@uTh:pBJ-ϓ}Dfq!?scDzKnW)PG"n{8/t*"9cYqѿGlf|Z4rGf@;;KŢb:׻qn/]ҵQpn#1)UdDxӢI_ʾ=8Zra 0 1A˜Ϗa`kRRզYJ?;/3P;RxP9'H[J+$N>q w,+a9*:zۇ?"!xm |b_ϰ2m:)t ̜ ms B"Bo`ɐ024[!ZD98e0HqIyz08+ N~.?A*9X8Oc풼1YVR]N`9[ thslxGx$ ZE344WB$ )Ib˭4$J[.!]cPRqo%"،ыw+qyȬ%0vFrquV+-go8O #*o ^3b\(<{B0dgvliP UE 9Wr}G5o2b?e`; К+-\Yn9wu=!n`H ͐v3sK͈0 <cotHH{~s㡇Dv7F;*hE\nDb1nȵ]v{Rl"xJ;a0[66j`bfPuR4oBȰXh0`fܽ="2][$q?!gDafwAAN3$9hse~]oC~"|G]h_~^(ESEj,4#N3lfqiEf ѤJ(ahA/Rlm4'I%t1<3+x8ePwm/O9+ojm/~E᧾b-$aT$L0^BWu'hXA͙ S|M:꽓3YDMlc?W[:d?trQ(^xig4tӣ'̙ϱA e@`蠼#wLQ3Ay|KmOgx@wo5 W g &!d 0mGFraKL&^]z^v:ҧ_)>{%NOF lrZ[D̶ITn9i%ߊ}PAhSV3JqOnaek(R ℐYl{J^|[qV,{ U!{Zx c{Z7hM~f@k5ޓ96n[;t` C,/jnLH()SF]:VS'޹JkAd0}ȁL{LPaBra ݯ/<|O=8]~>O_yaEn|_gw]&J[,. aC \?p8I2$Ycbd 8EFs(j1Qy6_+юuUIi$JG0gKxbڵ7,Aloҽs|( mh!q2["I }~٫Q4XsF0&{FhDD46a0 Jw8HɌF+}UWU }u.^V4@(]lfIS;rG. *Un`)#Ş~\oD8`+e.*uyY fĮFLPʓA:Iߡ|eN;@jƫŝe.fODyݍ͍2, i߱ 뢼C'p)Rs9K`^djkXam5dY^ p1DIn;~wnqgg<B#DaHrZ~\j1d{;cu5$ al 2hYl+ K4(U1մ㵂ɊE? =囼lH26b~zo|y=hWBw@cj&'5+o_>SMg p3reH?z+6h &0y\9 (ol#k _H#r4"Ch7Ȳ Iu-w:;͝ApZvSp>@IfJ㏮rcEL#ݏG9_-W^ WGA^|?>?yWl!Ũ^X[ǧ >2hy8gطsalRﱮ#$B 1hBg{h4ÏhaJ^IҘaC&ܸɲEns*e{ަ -d{PL)|,xɗ}ہTN& ?&v)*LON3aYc--l߶'xèdpνk1_?U:R90ms|qu/ O>_0AŸT<sY/O^G\Z0RuȸXAaIn2AjӇ. O&U\:aԒ rۚV!b$TrpipsT;_BԌh%'i %%&PD0B.#oΒ x<ѻ< 2Ha3y`3^Lu> w^;z9=*#4(` je0%i`D4!a#\EL2"{/! Pv27Փ; \i:gu/zgw7|( 6yfxvhX@+5a#'zn_oVoL 2:.oΠaO䯞w.%0:_b}. 4}߰ 9ԥJymdPNJ Fi 9g}BK4b]vw A]l1oKķ hҍҮq8q\>h/{D{Li/6oYK$\EEHH+fc<3.cok^WXo#I! C1h8ƻ|\~L2 YS## ƛ*Z#YYv77vv:lnΝ[ti#O&6 | 7n__ |;{c/jRH2BTbȪUxZoC*gDhW, ^{yn߼FY1 T$2uI~>q1{HVlMS߸7W'ۮ<ʥsh4\] 4LtY\+YxJoD(e&:v+)Q9S蠔me&h"*s&>W F>2`wO)lQ?Ϛ"Iyf$y1SN09*|CkZ=pl`-6PVԍ):i  :P\GR S)au@.[‰)@R$5JhRk2#[ȓ9Ds'9sD` DMQkp~E(؇͠L?+x*`ъaXԴiJ$?hTV I~/d? +l"Gb  jњ{_̴5QJGv%(疅VCpjvR.r66GW DA<<6f2pc3fx?ZI#UA~bBy1:{tf$Pc%,TȞ ,4 ГRhHQ0Qp"1a.(֖lnw,/a0T9{< "fȠ:ltg &ɟB@ ^:ǥ1*N,[oDZsz/ {JѶu Š>s. I:J~ɍVCrx_V,/vTxynu+#Yp%2d0VqYK=D<}K* ?z҂/COhAI3 zKwh9z~n 0@FOA;7#޳XQↈO(g/O߈I@qR5Hkw$-Ԕ,Qxw"TVőaH፻4A3ڒx͡tVtyp g7 ޒGl.f*1O32|5bPd@})"RҡAucol-@4olTT4>:G[^}R{Z1Ȟmq9e%%C?Sqrʩ_t@tJM:{=[H6r{lrG'(]@8z'3 ٸ!K'fC|~OͺU/oprJpy{2;ȏf\g>AwTjpOùS@O> Z@W[:vyL2 +WM~h{uLȜf*3XBwOy8ټ'ekαen%f̍ F^&=9XL]?U,$5䓣T-:AH&w/|~8>qX|azoF917q"8AlIRGy#I'd7q>W ̗{G8t3~Ef=qlRM_I OOP/~{h*δ'}^N܌'qbmׁgy{'g 0>S@c?C_?Y~T?qƌ%tEXtdate:create2014-03-05T13:56:33-05:00Ȝ%tEXtdate:modify2014-03-05T13:56:33-05:00p tEXtSoftwarewww.inkscape.org<IENDB`photocollage-1.4.3/bin/0000775000175000017500000000000012716673066015515 5ustar adrienadrien00000000000000photocollage-1.4.3/bin/photocollage0000775000175000017500000000164412716673056020127 0ustar adrienadrien00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (C) 2013 Adrien Vergé # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License along # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. import signal from photocollage import gtkgui if __name__ == '__main__': signal.signal(signal.SIGINT, signal.SIG_DFL) gtkgui.main()