├── .flake8 ├── .github └── workflows │ └── flake8.yml ├── .gitignore ├── .pylintrc ├── .travis.yml ├── LICENSE ├── README.md ├── __init__.py ├── binaryIO.py ├── enums.py ├── gimp-wilber.png ├── gimpFormat.py ├── gimpFormats.psproj ├── gimpGbrBrush.py ├── gimpGgrGradient.py ├── gimpGihBrushSet.py ├── gimpGpbBrush.py ├── gimpGplPalette.py ├── gimpGtpToolPreset.py ├── gimpIOBase.py ├── gimpImageInternals.py ├── gimpParasites.py ├── gimpPatPattern.py ├── gimpVbrBrush.py ├── gimpVectors.py ├── gimpXcfDocument.py ├── install.bat ├── notes ├── GIMP - GIMP Batch Mode.URL ├── Gimp - Write GIMP extensionsplug-insload- and save-handlers in Perl - metacpan.org.URL ├── appxcf · master · GNOME GIMP · GitLab.URL ├── devel-docsxcf.txt · master · GNOME GIMP · GitLab.URL └── gimp formats in txt files.URL ├── py.typed ├── pylint.rc ├── setup.py └── test ├── cli_help ├── __init__.py └── test.py ├── exportedPaths ├── README.txt ├── __init__.py └── lips.svg ├── gbrBrush ├── __init__.py ├── desiredOutput_dunes.png ├── desiredOutput_pepper.png ├── dunes.gbr ├── pepper.gbr └── test.py ├── ggrGradient ├── Cold_Steel_2.ggr ├── Mexican_flag.ggr ├── __init__.py └── test.py ├── gihBrushSet ├── Wilber.gih ├── __init__.py ├── actual_Wilber.gih ├── actual_feltpen.gih ├── desiredOutput_Wilber_01.png ├── desiredOutput_Wilber_02.png ├── desiredOutput_Wilber_03.png ├── desiredOutput_Wilber_04.png ├── desiredOutput_feltpen_01.png ├── desiredOutput_feltpen_02.png ├── desiredOutput_feltpen_03.png ├── desiredOutput_feltpen_04.png ├── desiredOutput_feltpen_05.png ├── desiredOutput_feltpen_06.png ├── desiredOutput_feltpen_07.png ├── desiredOutput_feltpen_08.png ├── desiredOutput_feltpen_09.png ├── desiredOutput_feltpen_10.png ├── desiredOutput_feltpen_100.png ├── desiredOutput_feltpen_101.png ├── desiredOutput_feltpen_102.png ├── desiredOutput_feltpen_103.png ├── desiredOutput_feltpen_104.png ├── desiredOutput_feltpen_105.png ├── desiredOutput_feltpen_106.png ├── desiredOutput_feltpen_107.png ├── desiredOutput_feltpen_108.png ├── desiredOutput_feltpen_109.png ├── desiredOutput_feltpen_11.png ├── desiredOutput_feltpen_110.png ├── desiredOutput_feltpen_111.png ├── desiredOutput_feltpen_112.png ├── desiredOutput_feltpen_113.png ├── desiredOutput_feltpen_114.png ├── desiredOutput_feltpen_115.png ├── desiredOutput_feltpen_116.png ├── desiredOutput_feltpen_117.png ├── desiredOutput_feltpen_118.png ├── desiredOutput_feltpen_119.png ├── desiredOutput_feltpen_12.png ├── desiredOutput_feltpen_120.png ├── desiredOutput_feltpen_121.png ├── desiredOutput_feltpen_122.png ├── desiredOutput_feltpen_123.png ├── desiredOutput_feltpen_124.png ├── desiredOutput_feltpen_125.png ├── desiredOutput_feltpen_13.png ├── desiredOutput_feltpen_14.png ├── desiredOutput_feltpen_15.png ├── desiredOutput_feltpen_16.png ├── desiredOutput_feltpen_17.png ├── desiredOutput_feltpen_18.png ├── desiredOutput_feltpen_19.png ├── desiredOutput_feltpen_20.png ├── desiredOutput_feltpen_21.png ├── desiredOutput_feltpen_22.png ├── desiredOutput_feltpen_23.png ├── desiredOutput_feltpen_24.png ├── desiredOutput_feltpen_25.png ├── desiredOutput_feltpen_26.png ├── desiredOutput_feltpen_27.png ├── desiredOutput_feltpen_28.png ├── desiredOutput_feltpen_29.png ├── desiredOutput_feltpen_30.png ├── desiredOutput_feltpen_31.png ├── desiredOutput_feltpen_32.png ├── desiredOutput_feltpen_33.png ├── desiredOutput_feltpen_34.png ├── desiredOutput_feltpen_35.png ├── desiredOutput_feltpen_36.png ├── desiredOutput_feltpen_37.png ├── desiredOutput_feltpen_38.png ├── desiredOutput_feltpen_39.png ├── desiredOutput_feltpen_40.png ├── desiredOutput_feltpen_41.png ├── desiredOutput_feltpen_42.png ├── desiredOutput_feltpen_43.png ├── desiredOutput_feltpen_44.png ├── desiredOutput_feltpen_45.png ├── desiredOutput_feltpen_46.png ├── desiredOutput_feltpen_47.png ├── desiredOutput_feltpen_48.png ├── desiredOutput_feltpen_49.png ├── desiredOutput_feltpen_50.png ├── desiredOutput_feltpen_51.png ├── desiredOutput_feltpen_52.png ├── desiredOutput_feltpen_53.png ├── desiredOutput_feltpen_54.png ├── desiredOutput_feltpen_55.png ├── desiredOutput_feltpen_56.png ├── desiredOutput_feltpen_57.png ├── desiredOutput_feltpen_58.png ├── desiredOutput_feltpen_59.png ├── desiredOutput_feltpen_60.png ├── desiredOutput_feltpen_61.png ├── desiredOutput_feltpen_62.png ├── desiredOutput_feltpen_63.png ├── desiredOutput_feltpen_64.png ├── desiredOutput_feltpen_65.png ├── desiredOutput_feltpen_66.png ├── desiredOutput_feltpen_67.png ├── desiredOutput_feltpen_68.png ├── desiredOutput_feltpen_69.png ├── desiredOutput_feltpen_70.png ├── desiredOutput_feltpen_71.png ├── desiredOutput_feltpen_72.png ├── desiredOutput_feltpen_73.png ├── desiredOutput_feltpen_74.png ├── desiredOutput_feltpen_75.png ├── desiredOutput_feltpen_76.png ├── desiredOutput_feltpen_77.png ├── desiredOutput_feltpen_78.png ├── desiredOutput_feltpen_79.png ├── desiredOutput_feltpen_80.png ├── desiredOutput_feltpen_81.png ├── desiredOutput_feltpen_82.png ├── desiredOutput_feltpen_83.png ├── desiredOutput_feltpen_84.png ├── desiredOutput_feltpen_85.png ├── desiredOutput_feltpen_86.png ├── desiredOutput_feltpen_87.png ├── desiredOutput_feltpen_88.png ├── desiredOutput_feltpen_89.png ├── desiredOutput_feltpen_90.png ├── desiredOutput_feltpen_91.png ├── desiredOutput_feltpen_92.png ├── desiredOutput_feltpen_93.png ├── desiredOutput_feltpen_94.png ├── desiredOutput_feltpen_95.png ├── desiredOutput_feltpen_96.png ├── desiredOutput_feltpen_97.png ├── desiredOutput_feltpen_98.png ├── desiredOutput_feltpen_99.png ├── feltpen.gih └── test.py ├── gpbBrush └── __init__.py ├── gplPalette ├── Plasma.gpl ├── __init__.py └── test.py ├── gtpToolPreset ├── 4_3-Landscape.gtp ├── Smudge-Rough.gtp ├── __init__.py └── test.py ├── layerGroups ├── __init__.py ├── layer_groups.png └── layer_groups.xcf ├── patPattern ├── 3dgreen.pat ├── __init__.py ├── desiredOutput_3dgreen.png ├── desiredOutput_leopard.png ├── leopard.pat └── test.py ├── simpleXcfRead ├── __init__.py ├── desiredOutput.png ├── one_layer_with_transparency.xcf └── test.py ├── test.py ├── twoLayers ├── __init__.py └── two_layers.xcf ├── vbrBrush ├── Diagonal-Star-17.vbr ├── __init__.py └── test.py ├── withPaths ├── __init__.py └── with_paths.xcf └── xcfWithSettings ├── __init__.py ├── test.py └── with_settings.xcf /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = 3 | E231 4 | E128 5 | E225 6 | E226 7 | E228 8 | E124 9 | E125 10 | E261 11 | E265 12 | E252 13 | E301 14 | E123 15 | E302 16 | E227 17 | E305 18 | E262 19 | E306 20 | E221 21 | E222 22 | 23 | -------------------------------------------------------------------------------- /.github/workflows/flake8.yml: -------------------------------------------------------------------------------- 1 | name: Flake8 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ["3.8", "3.9", "3.10"] 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Set up Python ${{ matrix.python-version }} 14 | uses: actions/setup-python@v3 15 | with: 16 | python-version: ${{ matrix.python-version }} 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip 20 | pip install pillow flake8 21 | - name: Analysing the code with pylint 22 | run: | 23 | flake8 $(git ls-files '*.py') 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | firefox_profile/* 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .pypirc 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # dotenv 86 | .env 87 | 88 | # virtualenv 89 | .venv 90 | venv/ 91 | ENV/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial # required for Python >= 3.7 2 | language: python 3 | python: 4 | - "3.7" 5 | 6 | # command to install dependencies 7 | install: 8 | - pip install codecov 9 | - pip install pillow 10 | - pip install scipy 11 | 12 | # command to run tests 13 | script: cd test && coverage run test.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gimpFormats 2 | A pure python implementation of the GIMP xcf image format. 3 | 4 | This was created primarily to serve as a file conversion tool for my smartimage library (coming soon). The idea is you can "upgrade" from a GIMP document to a smartimage. 5 | 6 | That being said, it should be generally useful to those who want to fiddle with GIMP files using Python. 7 | 8 | Currently supports: 9 | 10 | * Loading xcf files (up to current GIMP version 2.10) 11 | * Getting image hierarchy and info 12 | * Getting image for each layer (PIL image) 13 | * .gbr brushes 14 | * .vbr brushes 15 | * .gpl palette files 16 | * .pat pattern files 17 | 18 | Currently testing/unstable: 19 | 20 | * Saving 21 | * Programatically alter documents (add layer, etc) 22 | * gimp .gtp tool preset files - scheme file format is difficult to parse 23 | * .ggr gradients - reads/saves fine, but I need to come up with a way to get the actual colors 24 | * .gih brush sets - BUG: seems to have more image data per brush than what's expected 25 | * .gpb brush - should work, but I need some test files 26 | 27 | Currently not implemented: 28 | 29 | * Rendering a final, compositied image 30 | * Exported paths in .svg format. - Reading should be easy enough, but I need to ensure I don't get a full-blown svg in the mix 31 | * Standard "parasites" 32 | 33 | The home for this project is on my website, here: 34 | https://theheadlesssourceman.wordpress.com/2019/05/10/gimpformats/ 35 | 36 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Pure python implementation of the gimp file formats 3 | 4 | @supports: pyformatgenie 5 | """ 6 | from .gimpFormat import * 7 | from .gimpGbrBrush import * 8 | from .gimpGgrGradient import * 9 | from .gimpGihBrushSet import * 10 | from .gimpGpbBrush import * 11 | from .gimpGtpToolPreset import * 12 | from .gimpImageInternals import * 13 | from .gimpIOBase import * 14 | from .gimpParasites import * 15 | from .gimpPatPattern import * 16 | from .gimpVbrBrush import * 17 | from .gimpVectors import * 18 | from .gimpXcfDocument import * 19 | from .gimpGplPalette import * -------------------------------------------------------------------------------- /enums.py: -------------------------------------------------------------------------------- 1 | """ 2 | Enumerated types common to gimp formats 3 | """ 4 | import typing 5 | from enum import Enum 6 | 7 | 8 | COLOR_MODES=Enum('COLOR_MODES',start=0,names=( 9 | 'RGB','Grayscale','Indexed' 10 | )) 11 | 12 | UNITS=Enum('UNITS',start=0,names=( 13 | 'Inches','Millimeters','Points','Picas' 14 | )) 15 | UNITS_TO_MM:typing.Tuple[float,float,float,float]=\ 16 | (25.4,1,127/360.0,127/30.0) 17 | 18 | COMPOSITE_MODES=Enum('UNITS',start=-3,names=( 19 | '(auto) Intersection','(auto) Clip to layer','(auto) Clip to backdrop', 20 | 'Union','Clip to backdrop','Clip to layer','Intersection' 21 | )) 22 | 23 | COMPOSITE_SPACES=Enum('COMPOSITE_SPACES',start=-3,names=( 24 | '(Auto) LAB','(Auto) RGB (perceptual)','(Auto) RGB (linear)', 25 | 'RGB (linear)','RGB (perceptual)','LAB' 26 | )) 27 | 28 | TAG_COLORS=Enum('TAG_COLORS',start=0,names=( 29 | 'None','Blue','Green','Yellow','Orange','Brown','Red','Violet','Gray' 30 | )) 31 | COMPRESSION_MODES=Enum('COMPRESSION_MODES',start=0,names=( 32 | 'None','RLE','Zlib','Fractal' 33 | )) 34 | 35 | BLEND_MODES=Enum('BLEND_MODES',start=0,names=( 36 | 'Normal (legacy)', 37 | 'Dissolve (legacy)', 38 | 'Behind (legacy)', 39 | 'Multiply (legacy)', 40 | 'Screen (legacy)', 41 | 'Old broken Overlay', 42 | 'Difference (legacy)', 43 | 'Addition (legacy)', 44 | 'Subtract (legacy)', 45 | 'Darken only (legacy)', 46 | 'Lighten only (legacy)', 47 | 'Hue (HSV) (legacy)', 48 | 'Saturation (HSV) (legacy)', 49 | 'Color (HSL) (legacy)', 50 | 'Value (HSV) (legacy)', 51 | 'Divide (legacy)', 52 | 'Dodge (legacy)', 53 | 'Burn (legacy)', 54 | 'Hard Light (legacy)', 55 | 'Soft light (legacy)', 56 | 'Grain extract (legacy)', 57 | 'Grain merge (legacy)', 58 | 'Color erase (legacy)', 59 | 'Overlay', 60 | 'Hue (LCH)', 61 | 'Chroma (LCH)', 62 | 'Color (LCH)', 63 | 'Lightness (LCH)', 64 | 'Normal', 65 | 'Behind', 66 | 'Multiply', 67 | 'Screen', 68 | 'Difference', 69 | 'Addition', 70 | 'Substract', 71 | 'Darken only', 72 | 'Lighten only', 73 | 'Hue (HSV)', 74 | 'Saturation (HSV)', 75 | 'Color (HSL)', 76 | 'Value (HSV)', 77 | 'Divide', 78 | 'Dodge', 79 | 'Burn', 80 | 'Hard light', 81 | 'Soft light', 82 | 'Grain extract', 83 | 'Grain merge', 84 | 'Vivid light', 85 | 'Pin light', 86 | 'Linear light', 87 | 'Hard mix', 88 | 'Exclusion', 89 | 'Linear burn', 90 | 'Luma/Luminance darken only', 91 | 'Luma/Luminance lighten only', 92 | 'Luminance', 93 | 'Color erase', 94 | 'Erase', 95 | 'Merge', 96 | 'Split', 97 | 'Pass through')) 98 | 99 | PROPS=Enum('PROPS',start=0,names=( 100 | 'END', 101 | 'COLORMAP', 102 | 'ACTIVE_LAYER', 103 | 'ACTIVE_CHANNEL', 104 | 'SELECTION', 105 | 'FLOATING_SELECTION', 106 | 'OPACITY', 107 | 'MODE', 108 | 'VISIBLE', 109 | 'LINKED', 110 | 'LOCK_ALPHA', 111 | 'APPLY_MASK', 112 | 'EDIT_MASK', 113 | 'SHOW_MASK', 114 | 'SHOW_MASKED', 115 | 'OFFSETS', 116 | 'COLOR', 117 | 'COMPRESSION', 118 | 'GUIDES', 119 | 'RESOLUTION', 120 | 'TATTOO', 121 | 'PARASITES', 122 | 'UNIT', 123 | 'PATHS', 124 | 'USER_UNIT', 125 | 'VECTORS', 126 | 'TEXT_LAYER_FLAGS', 127 | 'OLD_SAMPLE_POINTS', 128 | 'LOCK_CONTENT', 129 | 'GROUP_ITEM', 130 | 'ITEM_PATH', 131 | 'GROUP_ITEM_FLAGS', 132 | 'LOCK_POSITION', 133 | 'FLOAT_OPACITY', 134 | 'COLOR_TAG', 135 | 'COMPOSITE_MODE', 136 | 'COMPOSITE_SPACE', 137 | 'BLEND_SPACE', 138 | 'FLOAT_COLOR', 139 | 'SAMPLE_POINTS', 140 | 'NUM_PROPS' 141 | )) 142 | -------------------------------------------------------------------------------- /gimp-wilber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/gimp-wilber.png -------------------------------------------------------------------------------- /gimpFormat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Pure python implementation of the gimp file formats 5 | """ 6 | import typing 7 | from gimpFormats.gimpXcfDocument import GimpDocument 8 | 9 | 10 | register=False 11 | 12 | 13 | class GimpFormatPlugin: 14 | """ 15 | A plugin to the gimp format 16 | """ 17 | # TODO: implement this? 18 | 19 | 20 | # ========= automatically add format info for pyformatgenie ========= 21 | if register: 22 | try: 23 | # will run this every time this module is loaded 24 | import pyformatgenie # type:ignore 25 | pfg=pyformatgenie.PyFormatGenie() 26 | pfg.add(GimpFormatPlugin) 27 | # and the most important part... 28 | pfg.save() 29 | except ImportError: 30 | # pyformatgenie is not installed (yet?). Continue with 31 | # whatever you came here for. 32 | pass 33 | 34 | 35 | def cmdline(args:typing.Iterable[str])->int: 36 | """ 37 | Run the command line 38 | 39 | :param args: command line arguments (WITHOUT the filename) 40 | """ 41 | printhelp=False 42 | if not args: 43 | printhelp=True 44 | else: 45 | g:typing.Optional[GimpDocument]=None 46 | for arg in args: 47 | if arg.startswith('-'): 48 | kv=[a.strip() for a in arg.split('=',1)] 49 | if kv[0] in ('-h','--help'): 50 | printhelp=True 51 | elif kv[0]=='--dump': 52 | print(g) 53 | elif kv[0]=='--showLayer': 54 | if g is None: 55 | print('ERR: No image') 56 | continue 57 | if kv[1]=='*': 58 | for n,layer in enumerate(g.layers): 59 | i=layer.image 60 | if i is None: 61 | print('No image for layer',n) 62 | else: 63 | print('showing layer',n) 64 | i.show() 65 | else: 66 | i=g.layers[int(kv[1])].image 67 | if i is None: 68 | print('No image for layer',int(kv[1])) 69 | else: 70 | print('showing layer',kv[1]) 71 | i.show() 72 | elif kv[0]=='--saveLayer': 73 | if g is None: 74 | print('ERR: No image') 75 | continue 76 | layerName=kv[1].split(',',1) 77 | if len(layerName)>1: 78 | filename=layerName[1] 79 | else: 80 | filename='layer *.png' 81 | layerId=kv[1][0] 82 | if layerId=='*': 83 | if filename.find('*')<0: 84 | fnParts=filename.split('.',1) 85 | fnParts.insert(1,'*') 86 | filename='.'.join(fnParts) 87 | for n,layer in enumerate(g.layers): 88 | i=layer.image 89 | if i is None: 90 | print('No image for layer',n) 91 | else: 92 | fn2=filename.replace('*',str(n)) 93 | print('saving layer',fn2) 94 | i.save(fn2) 95 | else: 96 | i=g.layers[int(layerId)].image 97 | if i is None: 98 | print('No image for layer',layerId) 99 | else: 100 | i.save(filename.replace('*',layerId)) 101 | else: 102 | print(f'ERR: unknown argument "{arg}"') 103 | else: 104 | g=GimpDocument(arg) 105 | if printhelp: 106 | print('Usage:') 107 | print(' gimpFormat.py file.xcf [options]') 108 | print('Options:') 109 | print(' -h, --help ............ this help screen') 110 | print(' --dump ................ dump info about this file') 111 | print(' --showLayer=n ......... show layer(s) (use * for all)') 112 | print(' --saveLayer=n,out.jpg . save layer(s) out to file') 113 | print(' --register ............ register this extension') 114 | return -1 115 | return 0 116 | 117 | 118 | if __name__=='__main__': 119 | import sys 120 | cmdline(sys.argv[1:]) 121 | -------------------------------------------------------------------------------- /gimpFormats.psproj: -------------------------------------------------------------------------------- 1 | [PyScripter] 2 | Version=3.6.3.0 3 | 4 | [Project] 5 | ClassName=TProjectRootNode 6 | StoreRelativePaths=TRUE 7 | ShowFileExtensions=FALSE 8 | 9 | [Project\ChildNodes\Node0] 10 | ClassName=TProjectFilesNode 11 | 12 | [Project\ChildNodes\Node0\ChildNodes\Node0] 13 | ClassName=TProjectFileNode 14 | FileName=$[Project-Path]__init__.py 15 | 16 | [Project\ChildNodes\Node0\ChildNodes\Node1] 17 | ClassName=TProjectFileNode 18 | FileName=$[Project-Path]binaryIO.py 19 | 20 | [Project\ChildNodes\Node0\ChildNodes\Node2] 21 | ClassName=TProjectFileNode 22 | FileName=$[Project-Path]gimpFormat.py 23 | 24 | [Project\ChildNodes\Node0\ChildNodes\Node3] 25 | ClassName=TProjectFileNode 26 | FileName=$[Project-Path]gimpGbrBrush.py 27 | 28 | [Project\ChildNodes\Node0\ChildNodes\Node4] 29 | ClassName=TProjectFileNode 30 | FileName=$[Project-Path]gimpGgrGradient.py 31 | 32 | [Project\ChildNodes\Node0\ChildNodes\Node5] 33 | ClassName=TProjectFileNode 34 | FileName=$[Project-Path]gimpGihBrushSet.py 35 | 36 | [Project\ChildNodes\Node0\ChildNodes\Node6] 37 | ClassName=TProjectFileNode 38 | FileName=$[Project-Path]gimpGpbBrush.py 39 | 40 | [Project\ChildNodes\Node0\ChildNodes\Node7] 41 | ClassName=TProjectFileNode 42 | FileName=$[Project-Path]gimpGplPalette.py 43 | 44 | [Project\ChildNodes\Node0\ChildNodes\Node8] 45 | ClassName=TProjectFileNode 46 | FileName=$[Project-Path]gimpGtpToolPreset.py 47 | 48 | [Project\ChildNodes\Node0\ChildNodes\Node9] 49 | ClassName=TProjectFileNode 50 | FileName=$[Project-Path]gimpImageInternals.py 51 | 52 | [Project\ChildNodes\Node0\ChildNodes\Node10] 53 | ClassName=TProjectFileNode 54 | FileName=$[Project-Path]gimpIOBase.py 55 | 56 | [Project\ChildNodes\Node0\ChildNodes\Node11] 57 | ClassName=TProjectFileNode 58 | FileName=$[Project-Path]gimpParasites.py 59 | 60 | [Project\ChildNodes\Node0\ChildNodes\Node12] 61 | ClassName=TProjectFileNode 62 | FileName=$[Project-Path]gimpPatPattern.py 63 | 64 | [Project\ChildNodes\Node0\ChildNodes\Node13] 65 | ClassName=TProjectFileNode 66 | FileName=$[Project-Path]gimpVbrBrush.py 67 | 68 | [Project\ChildNodes\Node0\ChildNodes\Node14] 69 | ClassName=TProjectFileNode 70 | FileName=$[Project-Path]gimpVectors.py 71 | 72 | [Project\ChildNodes\Node0\ChildNodes\Node15] 73 | ClassName=TProjectFileNode 74 | FileName=$[Project-Path]gimpXcfDocument.py 75 | 76 | [Project\ChildNodes\Node0\ChildNodes\Node16] 77 | ClassName=TProjectFileNode 78 | FileName=$[Project-Path]setup.py 79 | 80 | [Project\ChildNodes\Node0\ChildNodes] 81 | Count=17 82 | 83 | [Project\ChildNodes\Node1] 84 | ClassName=TProjectRunConfiguationsNode 85 | 86 | [Project\ChildNodes\Node1\ChildNodes\Node0] 87 | ClassName=TProjectRunConfiguationNode 88 | Name=all tests 89 | 90 | [Project\ChildNodes\Node1\ChildNodes\Node0\RunConfig] 91 | ScriptName=coverage -run C:\backed_up\computers\formats\gimpFormats\test\test.py 92 | Description=run all tests 93 | EngineType=peRemote 94 | ReinitializeBeforeRun=TRUE 95 | WorkingDir=$[ActiveScript-Dir] 96 | WriteOutputToFile=FALSE 97 | OutputFileName=$[ActiveScript-NoExt].log 98 | AppendToFile=FALSE 99 | 100 | [Project\ChildNodes\Node1\ChildNodes\Node0\RunConfig\ExternalRun] 101 | Caption=External Run 102 | Description=Run script using an external Python Interpreter 103 | ApplicationName=$[PythonExe-Short] 104 | Parameters=$[ActiveScript-Short] 105 | WorkingDirectory=$[ActiveScript-Dir] 106 | 107 | [Project\ChildNodes\Node1\ChildNodes\Node1] 108 | ClassName=TProjectRunConfiguationNode 109 | Name=simple xcf unit test 110 | 111 | [Project\ChildNodes\Node1\ChildNodes\Node1\RunConfig] 112 | ScriptName=C:\backed_up\computers\formats\gimpFormats\test\simpleXcfRead\test.py 113 | EngineType=peRemote 114 | ReinitializeBeforeRun=TRUE 115 | WorkingDir=$[ActiveScript-Dir] 116 | WriteOutputToFile=FALSE 117 | OutputFileName=$[ActiveScript-NoExt].log 118 | AppendToFile=FALSE 119 | 120 | [Project\ChildNodes\Node1\ChildNodes\Node1\RunConfig\ExternalRun] 121 | Caption=External Run 122 | Description=Run script using an external Python Interpreter 123 | ApplicationName=$[PythonExe-Short] 124 | Parameters=$[ActiveScript-Short] 125 | WorkingDirectory=$[ActiveScript-Dir] 126 | 127 | [Project\ChildNodes\Node1\ChildNodes\Node2] 128 | ClassName=TProjectRunConfiguationNode 129 | Name=show gimp image 130 | 131 | [Project\ChildNodes\Node1\ChildNodes\Node2\RunConfig] 132 | ScriptName=C:\backed_up\computers\formats\gimpFormats\gimpXcfDocument.py 133 | EngineType=peRemote 134 | ReinitializeBeforeRun=TRUE 135 | Parameters=test\simpleXcfRead\one_layer_with_transparency.xcf --show 136 | WorkingDir=$[ActiveScript-Dir] 137 | WriteOutputToFile=FALSE 138 | OutputFileName=$[ActiveScript-NoExt].log 139 | AppendToFile=FALSE 140 | 141 | [Project\ChildNodes\Node1\ChildNodes\Node2\RunConfig\ExternalRun] 142 | Caption=External Run 143 | Description=Run script using an external Python Interpreter 144 | ApplicationName=$[PythonExe-Short] 145 | Parameters=$[ActiveScript-Short] 146 | WorkingDirectory=$[ActiveScript-Dir] 147 | 148 | [Project\ChildNodes\Node1\ChildNodes\Node3] 149 | ClassName=TProjectRunConfiguationNode 150 | Name=save smartimage 151 | 152 | [Project\ChildNodes\Node1\ChildNodes\Node3\RunConfig] 153 | ScriptName=C:\backed_up\computers\formats\gimpFormats\gimpXcfDocument.py 154 | Description=saves out to test.simg 155 | EngineType=peRemote 156 | ReinitializeBeforeRun=TRUE 157 | Parameters=test\simpleXcfRead\one_layer_with_transparency.xcf --save=test.simg 158 | WorkingDir=$[ActiveScript-Dir] 159 | WriteOutputToFile=FALSE 160 | OutputFileName=$[ActiveScript-NoExt].log 161 | AppendToFile=FALSE 162 | 163 | [Project\ChildNodes\Node1\ChildNodes\Node3\RunConfig\ExternalRun] 164 | Caption=External Run 165 | Description=Run script using an external Python Interpreter 166 | ApplicationName=$[PythonExe-Short] 167 | Parameters=$[ActiveScript-Short] 168 | WorkingDirectory=$[ActiveScript-Dir] 169 | 170 | [Project\ChildNodes\Node1\ChildNodes] 171 | Count=4 172 | 173 | [Project\ChildNodes] 174 | Count=2 175 | 176 | [Project\ExtraPythonPath] 177 | Count=0 178 | 179 | -------------------------------------------------------------------------------- /gimpGbrBrush.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Pure python implementation of the gimp gbr brush format 5 | """ 6 | import typing 7 | import PIL.Image 8 | from gimpFormats.binaryIO import IO 9 | 10 | 11 | class GimpGbrBrush: 12 | """ 13 | Pure python implementation of the gimp gbr brush format 14 | 15 | See: 16 | https://gitlab.gnome.org/GNOME/gimp/blob/master/devel-docs/gbr.txt 17 | 18 | Format: 19 | name: GimpGbrBrush 20 | description: Gimp brush 21 | guid: {45129576-6728-4967-8888-6b9082862ca8} 22 | parentNames: Brush, Image 23 | #mimeTypes: application/jpeg 24 | filenamePatterns: *.gbr 25 | """ 26 | 27 | MAGIC_NUMBER:typing.Tuple[int,str]=(0,'GIMP') 28 | 29 | COLOR_MODES:typing.List[typing.Optional[str]]=\ 30 | [None,'L','LA','RGB','RGBA'] # only L or RGB allowed 31 | 32 | def __init__(self, 33 | filename:typing.Optional[str]=None): 34 | """ """ 35 | self.filename:typing.Optional[str]=None 36 | self.version:float=2 37 | self.width:int=0 38 | self.height:int=0 39 | self.bpp:int=1 40 | self.mode:typing.Optional[str]=self.COLOR_MODES[self.bpp] 41 | self.name:str='' 42 | self.rawImageBytes:typing.Optional[bytes]=None 43 | self.spacing:int=0 44 | if filename is not None: 45 | self.load(filename) 46 | 47 | def load(self, 48 | filename:typing.Union[str,typing.BinaryIO] 49 | )->int: 50 | """ 51 | load a gimp file 52 | 53 | :param filename: can be a file name or a file-like object 54 | """ 55 | if not isinstance(filename,str): 56 | self.filename=filename.name 57 | data=filename.read() 58 | else: 59 | self.filename=filename 60 | f=open(filename,'rb') 61 | data=f.read() 62 | f.close() 63 | return self._decode_(data) 64 | 65 | def _decode_(self,data:bytes,index:int=0)->int: 66 | """ 67 | decode a byte buffer 68 | 69 | :param data: data buffer to decode 70 | :param index: index within the buffer to start at 71 | 72 | :return: the number of bytes read 73 | """ 74 | io=IO(data,index) 75 | headerSize=io.u32 76 | self.version=io.u32 77 | if self.version!=2: 78 | msg=f'ERR:unknown brush version {self.version} at index {io.index}' 79 | raise Exception(msg) 80 | self.width=io.u32 81 | self.height=io.u32 82 | self.bpp=io.u32 # only allows grayscale or RGB 83 | self.mode=self.COLOR_MODES[self.bpp] 84 | magic=io.getBytes(4) 85 | if magic.decode('ascii')!='GIMP': 86 | magicName=magic.decode('ascii') 87 | msg='File format error. Magic value mismatch' 88 | msg=f'{msg}:"{magicName}" {index}' 89 | raise ImportError(msg) 90 | self.spacing=io.u32 91 | nameLen=headerSize-io.index 92 | self.name=io.getBytes(nameLen).decode('UTF-8') 93 | self.rawImageBytes=io.getBytes(self.width*self.height*self.bpp) 94 | return io.index 95 | 96 | def toBytes(self)->bytes: 97 | """ 98 | encode this object to raw bytes 99 | """ 100 | if self.rawImageBytes is None: 101 | return bytes() 102 | io=IO() 103 | io.u32=28+len(self.name) 104 | io.u32=self.version 105 | io.u32=self.width 106 | io.u32=self.height 107 | io.u32=self.bpp 108 | io.addBytes('GIMP') 109 | io.u32=self.spacing 110 | io.addBytes(self.name) 111 | io.addBytes(self.rawImageBytes) 112 | return io.data 113 | 114 | @property 115 | def size(self)->typing.Tuple[int,int]: 116 | """ 117 | Size of the brush 118 | """ 119 | return (self.width,self.height) 120 | 121 | @property 122 | def image(self)->typing.Optional[PIL.Image.Image]: 123 | """ 124 | get a final, compiled image 125 | """ 126 | if self.rawImageBytes is None: 127 | return None 128 | if self.mode is None: 129 | self.mode='L' 130 | raw=bytes(self.rawImageBytes) 131 | return PIL.Image.frombytes(self.mode,self.size,raw,decoder_name='raw') 132 | 133 | def show(self): 134 | """ 135 | show the brush image using the system image viewer 136 | """ 137 | if self.image is None: 138 | raise IndexError('No image data to save') 139 | self.image.show(title=self.name) 140 | 141 | def save(self, 142 | toFilename:typing.Optional[str]=None, 143 | toExtension:typing.Optional[str]=None 144 | )->None: 145 | """ 146 | save this gimp image to a file 147 | """ 148 | asImage=False 149 | if toFilename is None: 150 | if self.filename is None: 151 | self.filename='Untitled.gbr' 152 | toFilename=self.filename 153 | else: 154 | self.filename=toFilename 155 | if toFilename is None: 156 | toFilename='untitled.png' 157 | if toExtension is None: 158 | if toFilename is not None: 159 | toExtensionPath=toFilename.rsplit('.',1) 160 | if len(toExtensionPath)>1: 161 | toExtension=toExtensionPath[-1] 162 | else: 163 | toExtension=None 164 | if toExtension is not None and toExtension!='gbr': 165 | asImage=True 166 | if asImage: 167 | if self.image is None: 168 | raise IndexError('No image data to save') 169 | self.image.save(toFilename) 170 | else: 171 | if not hasattr(toFilename,'write'): 172 | f=open(toFilename,'wb') 173 | data=self.toBytes() 174 | if data is None: 175 | raise IndexError('No image data to save') 176 | f.write(data) 177 | 178 | def __repr__(self,indent:str='')->str: 179 | """ 180 | Get a textual representation of this object 181 | """ 182 | ret=[] 183 | if self.filename is not None: 184 | ret.append('Filename: '+self.filename) 185 | ret.append('Name: '+str(self.name)) 186 | ret.append('Version: '+str(self.version)) 187 | ret.append('Size: '+str(self.width)+' x '+str(self.height)) 188 | ret.append('Spacing: '+str(self.spacing)) 189 | ret.append('BPP: '+str(self.bpp)) 190 | ret.append('Mode: '+str(self.mode)) 191 | return ('\n'+indent).join(ret) 192 | 193 | 194 | def cmdline(args:typing.Iterable[str])->int: 195 | """ 196 | Run the command line 197 | 198 | :param args: command line arguments (WITHOUT the filename) 199 | """ 200 | printhelp=False 201 | if not args: 202 | printhelp=True 203 | else: 204 | g:typing.Optional[GimpGbrBrush]=None 205 | for arg in args: 206 | if arg.startswith('-'): 207 | kv=[a.strip() for a in arg.split('=',1)] 208 | if kv[0] in ['-h','--help']: 209 | printhelp=True 210 | elif kv[0]=='--dump': 211 | print(g) 212 | elif kv[0]=='--show': 213 | if g is None: 214 | print('ERR: no image to show') 215 | else: 216 | g.show() 217 | elif kv[0]=='--save': 218 | if g is None: 219 | print('ERR: no image to save') 220 | else: 221 | g.save(kv[1]) 222 | else: 223 | print(f'ERR: unknown argument "{arg}"') 224 | else: 225 | g=GimpGbrBrush(arg) 226 | if printhelp: 227 | print('Usage:') 228 | print(' gimpGbrBrush.py file.xcf [options]') 229 | print('Options:') 230 | print(' -h, --help ............ this help screen') 231 | print(' --dump ................ dump info about this file') 232 | print(' --show ................ show the brush image') 233 | print(' --save=out.jpg ........ save out the brush image') 234 | print(' --register ............ register this extension') 235 | return -1 236 | return 0 237 | 238 | 239 | if __name__=='__main__': 240 | import sys 241 | cmdline(sys.argv[1:]) 242 | -------------------------------------------------------------------------------- /gimpGgrGradient.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Gimp color gradient 5 | """ 6 | import typing 7 | from enum import Enum 8 | 9 | 10 | RgbaColorTuple=typing.Union[ 11 | typing.Tuple[int,int,int,int], 12 | typing.Tuple[float,float,float,float]] 13 | 14 | 15 | BLEND_FUNCTIONS=Enum('BLEND_FUNCTIONS',start=0,names=( 16 | "linear","curved","sinusoidal","spherical (increasing)", 17 | "spherical (decreasing)","step" 18 | )) 19 | 20 | COLOR_TYPES=Enum('COLOR_TYPES',start=0,names=( 21 | "RGB","HSV CCW","HSV CW" 22 | )) 23 | 24 | ENDPOINT_COLOR_TYPES=Enum('ENDPOINT_COLOR_TYPES',start=0,names=( 25 | "fixed","foreground","foreground transparent", 26 | "background","background transparent" 27 | )) 28 | 29 | class GradientSegment: 30 | """ 31 | Single segment within a gradient 32 | """ 33 | 34 | def __init__(self)->None: 35 | self.leftPosition:float=0 36 | self.middlePosition:float=0.5 37 | self.rightPosition:float=1.0 38 | self.leftColor:RgbaColorTuple=(0,0,0,0) 39 | self.rightColor:RgbaColorTuple=(255,255,255,0) 40 | self.blendFunc:typing.Optional[BLEND_FUNCTIONS]=None 41 | self.colorType:typing.Optional[COLOR_TYPES]=None 42 | self.leftColorType:typing.Optional[ENDPOINT_COLOR_TYPES]=None 43 | self.rightColorType:typing.Optional[ENDPOINT_COLOR_TYPES]=None 44 | 45 | def getColor(self,percent:float)->RgbaColorTuple: 46 | """ 47 | given a decimal percent (1.0 = 100%) retrieve 48 | the appropriate color for this point in the gradient 49 | """ 50 | raise NotImplementedError() 51 | 52 | def _decode_(self, 53 | data:typing.Union[str,typing.List[str]], 54 | index:int=0)->None: 55 | """ 56 | decode a byte buffer 57 | 58 | :param data: data buffer to decode 59 | :param index: index within the buffer to start at 60 | """ 61 | if isinstance(data,str): 62 | data=data.split(' ') 63 | if index!=0: 64 | data=data[index:] 65 | if len(data)<11 or len(data)>15: 66 | raise IndexError('Data table is unexpected size. '+str(len(data))) 67 | self.leftPosition=float(data[0]) 68 | self.middlePosition=float(data[1]) 69 | self.rightPosition=float(data[2]) 70 | self.leftColor=( 71 | float(data[3]),float(data[4]),float(data[5]),float(data[6])) 72 | self.rightColor=( 73 | float(data[7]),float(data[8]),float(data[9]),float(data[10])) 74 | if len(data)>=12: 75 | self.blendFunc=BLEND_FUNCTIONS(int(data[11])) 76 | if len(data)>=13: 77 | self.colorType=COLOR_TYPES(int(data[12])) 78 | if len(data)>=14: 79 | self.leftColorType=ENDPOINT_COLOR_TYPES(int(data[13])) 80 | if len(data)>=15: 81 | self.rightColorType=ENDPOINT_COLOR_TYPES(int(data[14])) 82 | 83 | def encode(self)->str: 84 | """ 85 | encode this to a string 86 | """ 87 | ret=[] 88 | ret.append("%06f"%self.leftPosition) 89 | ret.append("%06f"%self.middlePosition) 90 | ret.append("%06f"%self.rightPosition) 91 | for chan in self.leftColor: 92 | ret.append("%06f"%chan) 93 | for chan in self.rightColor: 94 | ret.append("%06f"%chan) 95 | if self.blendFunc is not None: 96 | ret.append(str(self.blendFunc)) 97 | if self.colorType is not None: 98 | ret.append(str(self.blendFunc)) 99 | if self.leftColorType is not None: 100 | ret.append(str(self.blendFunc)) 101 | if self.rightColorType is not None: 102 | ret.append(str(self.blendFunc)) 103 | return ' '.join(ret) 104 | 105 | def __repr__(self,indent:str='')->str: 106 | """ 107 | Get a textual representation of this object 108 | """ 109 | ret=[] 110 | ret.append(f'Left Position: {self.leftPosition}') 111 | ret.append(f'Middle Position: {self.middlePosition}') 112 | ret.append(f'Right Position: {self.rightPosition}') 113 | ret.append(f'Left Color: {self.leftColor}') 114 | ret.append(f'Right Color: {self.rightColor}') 115 | if self.blendFunc is None: 116 | s="None" 117 | else: 118 | s=self.blendFunc.name 119 | ret.append('Blend Function: '+s) 120 | if self.colorType is None: 121 | s="None" 122 | else: 123 | s=self.colorType.name 124 | ret.append('Color Type: '+s) 125 | if self.leftColorType is None: 126 | s="None" 127 | else: 128 | s=self.leftColorType.name 129 | ret.append('Left Color Type: '+s) 130 | if self.rightColorType is None: 131 | s="None" 132 | else: 133 | s=self.rightColorType.name 134 | ret.append('Right Color Type: '+s) 135 | return ('\n'+indent).join(ret) 136 | 137 | 138 | class GimpGgrGradient: 139 | """ 140 | Gimp golor gradient 141 | 142 | See: 143 | https://gitlab.gnome.org/GNOME/gimp/blob/master/devel-docs/ggr.txt 144 | 145 | Format: 146 | name: GimpGgrGradient 147 | description: Gimp color gradient file 148 | guid: {45129576-6728-4967-8888-6b9082862ca0} 149 | parentNames: ColorGradient 150 | #mimeTypes: application/jpeg 151 | filenamePatterns: *.ggr 152 | """ 153 | 154 | MAGIC_NUMBER=(0,'GIMP Gradient') 155 | 156 | def __init__(self,filename:typing.Optional[str]=None)->None: 157 | self.filename:typing.Optional[str]=None 158 | self.segments:typing.List[GradientSegment]=[] 159 | self.name:str='' 160 | if filename is not None: 161 | self.load(filename) 162 | 163 | def load(self, 164 | filename:typing.Union[str,typing.BinaryIO] 165 | )->None: 166 | """ 167 | load a gimp file 168 | 169 | :param filename: can be a file name or a file-like object 170 | """ 171 | if not isinstance(filename,str): 172 | self.filename=filename.name 173 | data=filename.read() 174 | else: 175 | self.filename=filename 176 | f=open(filename,'rb') 177 | data=f.read() 178 | f.close() 179 | self._decode_(data) 180 | 181 | def _decode_(self, 182 | data:typing.Union[bytes,str,typing.List[str]], 183 | index:int=0)->None: 184 | """ 185 | decode a byte buffer 186 | 187 | :param data: data buffer to decode 188 | :param index: index within the buffer to start at 189 | """ 190 | if isinstance(data,bytes): 191 | if index!=0: 192 | data=data[index:] 193 | data=data.decode('utf-8') 194 | if isinstance(data,str): 195 | data=data[index:].split('\n') 196 | data=[l.strip() for l in data] # noqa: E741 197 | if data[0]!='GIMP Gradient': 198 | raise Exception('File format error. Magic value mismatch.') 199 | self.name=data[1].split(':',1)[-1].strip() 200 | numSegments=int(data[2]) 201 | for i in range(numSegments): 202 | gs=GradientSegment() 203 | gs._decode_(data[i+3]) # pylint: disable=protected-access 204 | self.segments.append(gs) 205 | 206 | def encode(self)->str: 207 | """ 208 | encode this to a string 209 | """ 210 | ret=['GIMP Gradient'] 211 | ret.append('Name: '+self.name) 212 | ret.append(str(len(self.segments))) 213 | for segment in self.segments: 214 | ret.append(segment.encode()) 215 | return ('\n'.join(ret)+'\n') 216 | 217 | def toBytes(self)->bytes: 218 | """ 219 | encode this to bytes 220 | """ 221 | return self.encode().encode('utf-8') 222 | 223 | def save(self, 224 | toFilename:typing.Union[None,str,typing.BinaryIO]=None, 225 | toExtension:typing.Optional[str]=None 226 | )->None: 227 | """ 228 | save this gimp image to a file 229 | """ 230 | if toExtension is not None and toExtension!='ggr': 231 | msg=f'Unable to convert to extension "{toExtension}"' 232 | raise Exception(msg) 233 | if toFilename is None: 234 | if self.filename is None: 235 | self.filename='Untitled.ggr' 236 | toFilename=self.filename 237 | elif not isinstance(toFilename,str): 238 | toFilename=toFilename.name 239 | self.filename=toFilename 240 | else: 241 | if toFilename.rsplit('.',1)[-1].lower()!='ggr': 242 | msg=f'Unable to convert to extension "{toExtension}"' 243 | raise Exception(msg) 244 | self.filename=toFilename 245 | if not hasattr(toFilename,'write'): 246 | f=open(toFilename,'wb') 247 | f.write(self.toBytes()) 248 | 249 | def getColor(self,percent:float)->RgbaColorTuple: 250 | """ 251 | given a decimal percent (1.0 = 100%) retrieve 252 | the appropriate color for this point in the gradient 253 | """ 254 | raise NotImplementedError() 255 | 256 | def __repr__(self,indent:str='')->str: 257 | """ 258 | Get a textual representation of this object 259 | """ 260 | ret=[] 261 | if self.filename is not None: 262 | ret.append('Filename: '+self.filename) 263 | ret.append('Name: '+str(self.name)) 264 | for s in self.segments: 265 | ret.append(s.__repr__(indent+'\t')) 266 | return ('\n'+indent).join(ret) 267 | 268 | 269 | def cmdline(args:typing.Iterable[str])->int: 270 | """ 271 | Run the command line 272 | 273 | :param args: command line arguments (WITHOUT the filename) 274 | """ 275 | printhelp=False 276 | if not args: 277 | printhelp=True 278 | else: 279 | g:typing.Optional[GimpGgrGradient]=None 280 | for arg in args: 281 | if arg.startswith('-'): 282 | kv=[a.strip() for a in arg.split('=',1)] 283 | if kv[0] in ('-h','--help'): 284 | printhelp=True 285 | elif kv[0]=='--dump': 286 | print(g) 287 | else: 288 | print(f'ERR: unknown argument "{arg}"') 289 | else: 290 | g=GimpGgrGradient(arg) 291 | if printhelp: 292 | print('Usage:') 293 | print(' gimpGgrGradient.py file.xcf [options]') 294 | print('Options:') 295 | print(' -h, --help ............ this help screen') 296 | print(' --dump ................ dump info about this file') 297 | print(' --register ............ register this extension') 298 | return -1 299 | return 0 300 | 301 | 302 | if __name__=='__main__': 303 | import sys 304 | cmdline(sys.argv[1:]) 305 | -------------------------------------------------------------------------------- /gimpGihBrushSet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Gimp Image Pipe Format 5 | 6 | The gih format is use to store a series of brushes, and some extra info 7 | for how to use them. 8 | """ 9 | import typing 10 | from collections import OrderedDict 11 | from PIL.Image import Image 12 | from gimpFormats.binaryIO import IO 13 | from gimpFormats.gimpGbrBrush import GimpGbrBrush 14 | 15 | class GimpGihBrushSet: 16 | """ 17 | Gimp Image Pipe Format 18 | 19 | The gih format is use to store a series of brushes, and some extra info 20 | for how to use them. 21 | 22 | See: 23 | https://gitlab.gnome.org/GNOME/gimp/blob/master/devel-docs/gih.txt 24 | 25 | Format: 26 | name: GimpGihBrushSet 27 | description: Gimp brush set 28 | guid: {45129576-6728-4967-8888-6b9082862ca1} 29 | parentNames: [Brush] 30 | #mimeTypes: application/jpeg 31 | filenamePatterns: *.gih 32 | """ 33 | 34 | def __init__(self, 35 | filename:typing.Union[None,str,typing.BinaryIO]=None 36 | )->None: 37 | """ """ 38 | self.filename:typing.Optional[str]=None 39 | self.name:str='' 40 | self.params:OrderedDict=OrderedDict() 41 | self.brushes:typing.List[GimpGbrBrush]=[] 42 | if filename is not None: 43 | self.load(filename) 44 | 45 | @property 46 | def images(self)->typing.Iterable[Image]: 47 | """ 48 | The brush images 49 | """ 50 | ret=[] 51 | for brush in self.brushes: 52 | if brush.image is not None: 53 | ret.append(brush.image) 54 | return ret 55 | 56 | def load(self, 57 | filename:typing.Union[str,typing.BinaryIO] 58 | )->None: 59 | """ 60 | load a gimp file 61 | 62 | :param filename: can be a file name or a file-like object 63 | """ 64 | if isinstance(filename,str): 65 | self.filename=filename 66 | f=open(filename,'rb') 67 | data=f.read() 68 | f.close() 69 | else: 70 | self.filename=filename.name 71 | data=filename.read() 72 | self._decode_(data) 73 | 74 | def _decode_(self,data:bytes,index:int=0)->int: 75 | """ 76 | decode a byte buffer 77 | 78 | :param data: data buffer to decode 79 | :param index: index within the buffer to start at 80 | """ 81 | io=IO(data,index) 82 | self.name=io.textLine 83 | secondLine=io.textLine.split(' ') 84 | self.params=OrderedDict() 85 | numBrushes=int(secondLine[0]) 86 | # everything that's left in line 2 87 | # is a gimp-image-pipe-parameters parasite 88 | for i in range(1,len(secondLine)): 89 | param=secondLine[i].split(':',1) 90 | self.params[param[0].strip()]=param[1].strip() 91 | self.brushes=[] 92 | for _ in range(numBrushes): 93 | b=GimpGbrBrush() 94 | # NOTE: expects GimpGbrBrush._decode_ to be the num bytes read 95 | # I don't know whether this is right, wrong, or sideways, 96 | # but there you have it 97 | io.index+=b._decode_(io.data,io.index) # pylint: disable=protected-access # noqa: E501 98 | self.brushes.append(b) 99 | return io.index 100 | 101 | def toBytes(self)->bytes: 102 | """ 103 | encode this object to a byte array 104 | """ 105 | io=IO() 106 | io.textLine=self.name 107 | # add the second line of data 108 | secondLineA=[str(len(self.brushes))] 109 | for k,v in self.params.items(): 110 | secondLineA.append(k+':'+str(v)) 111 | secondLine=' '.join(secondLineA) 112 | io.textLine=secondLine 113 | # add the brushes 114 | for brush in self.brushes: 115 | #TODO: currently broken. This results in header insterted twice 116 | # for some reason! 117 | io.addBytes(brush.toBytes()) 118 | return io.data 119 | 120 | def save(self, 121 | toFilename:typing.Union[None,str,typing.BinaryIO]=None, 122 | toExtension:typing.Optional[str]=None 123 | )->None: 124 | """ 125 | save this gimp image to a file 126 | """ 127 | f=None 128 | if toFilename is None: 129 | if self.filename is None: 130 | self.filename='untitled.gih' 131 | toFilename=self.filename 132 | elif isinstance(toFilename,str): 133 | self.filename=toFilename 134 | else: 135 | f=toFilename 136 | toFilename=toFilename.name 137 | self.filename=toFilename 138 | if toExtension is None: 139 | toExtension=self.filename.rsplit('.',1)[-1] 140 | if toExtension=='gih': 141 | if f is None: 142 | f=open(toFilename,'wb') 143 | f.write(self.toBytes()) 144 | elif toExtension=='gbr': 145 | # export the brush set as a series of brush files 146 | baseFilename=toFilename.rsplit('.',1)[0] 147 | for i,brush in enumerate(self.brushes): 148 | modFilename=f'{baseFilename}_{i}.{toExtension}' 149 | brush.save(modFilename) 150 | else: 151 | # export the brush set as a series of image files 152 | baseFilename=toFilename.rsplit('.',1)[0] 153 | for i,brush in enumerate(self.brushes): 154 | modFilename=f'{baseFilename}_{i}.{toExtension}' 155 | brush.save(modFilename,toExtension) 156 | 157 | def __repr__(self,indent=''): 158 | """ 159 | Get a textual representation of this object 160 | """ 161 | ret=[] 162 | if self.filename is not None: 163 | ret.append('Filename: '+self.filename) 164 | ret.append('Name: '+str(self.name)) 165 | for k,v in list(self.params.items()): 166 | ret.append(k+': '+str(v)) 167 | for i,brush in enumerate(self.brushes): 168 | ret.append('Brush '+str(i)) 169 | ret.append(brush.__repr__(indent+'\t')) 170 | return ('\n'+indent).join(ret) 171 | 172 | 173 | def cmdline(args): 174 | """ 175 | Run the command line 176 | 177 | :param args: command line arguments (WITHOUT the filename) 178 | """ 179 | printhelp=False 180 | if not args: 181 | printhelp=True 182 | else: 183 | g=None 184 | for arg in args: 185 | if arg.startswith('-'): 186 | arg=[a.strip() for a in arg.split('=',1)] 187 | if arg[0] in ['-h','--help']: 188 | printhelp=True 189 | elif arg[0]=='--dump': 190 | print(g) 191 | elif arg[0]=='--show': 192 | if arg[1]=='*': 193 | for brush in g.brushes: 194 | brush.image.show() 195 | else: 196 | g.brushes[int(arg[1])].image.show() 197 | elif arg[0]=='--save': 198 | index,filename=arg[1].split(',',1) 199 | if filename.find('*')<0: 200 | filename='*.'.join(filename.split('.',1)) 201 | if index=='*': 202 | for i,brush in enumerate(g.brushes): 203 | fn2=filename.replace('*',str(i)) 204 | brush.image.save(fn2) 205 | else: 206 | fn2=filename.replace('*',i) 207 | g.brushes[int(index)].image.save(fn2) 208 | else: 209 | print(f'ERR: unknown argument "{arg}"') 210 | else: 211 | g=GimpGihBrushSet(arg) 212 | if printhelp: 213 | print('Usage:') 214 | print(' gimpGihBrushSet.py file.xcf [options]') 215 | print('Options:') 216 | print(' -h, --help ............ this help screen') 217 | print(' --dump ................ dump info about this file') 218 | print(' --show=n .............. show the brush image(s) n=* for all') 219 | print(' --save=n,out.jpg ...... save out the brush image(s)') 220 | print(' --register ............ register this extension') 221 | 222 | 223 | if __name__=='__main__': 224 | import sys 225 | cmdline(sys.argv[1:]) 226 | -------------------------------------------------------------------------------- /gimpGpbBrush.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Pure python implementation of the OLD gimp gpb brush format 5 | """ 6 | import typing 7 | from gimpFormats.binaryIO import IO 8 | from gimpFormats.gimpGbrBrush import GimpGbrBrush 9 | from gimpFormats.gimpPatPattern import GimpPatPattern 10 | 11 | 12 | class GimpGpbBrush: 13 | """ 14 | Pure python implementation of the OLD gimp gpb brush format 15 | 16 | See: 17 | https://developer.gimp.org/core/standards/gpb/ 18 | 19 | Format: 20 | name: GimpGpbBrush 21 | description: Gimp brush (legacy) 22 | guid: {45129576-6728-4967-8888-6b9082862ca2} 23 | parentNames: Brush 24 | #mimeTypes: application/jpeg 25 | filenamePatterns: *.gpb 26 | """ 27 | 28 | MAGIC_NUMBER:typing.Tuple[int,str]=(0,'GIMP') 29 | 30 | def __init__(self, 31 | filename:typing.Union[None,str,typing.BinaryIO]=None): 32 | """ """ 33 | self.brush:GimpGbrBrush=GimpGbrBrush() 34 | self.pattern:GimpPatPattern=GimpPatPattern() 35 | self.filename:typing.Optional[str]=None 36 | if filename is not None: 37 | self.load(filename) 38 | 39 | def load(self,filename:typing.Union[str,typing.BinaryIO])->None: 40 | """ 41 | load a gimp file 42 | 43 | :param filename: can be a file name or a file-like object 44 | """ 45 | if not isinstance(filename,str): 46 | self.filename=filename.name 47 | data=filename.read() 48 | else: 49 | self.filename=filename 50 | f=open(filename,'rb') 51 | data=f.read() 52 | f.close() 53 | self._decode_(data) 54 | 55 | def _decode_(self,data:bytes,index:int=0)->int: 56 | """ 57 | decode a byte buffer 58 | 59 | :param data: data buffer to decode 60 | :param index: index within the buffer to start at 61 | """ 62 | index=self.brush._decode_(data,index) # pylint: disable=protected-access # noqa: E501 63 | index=self.pattern._decode_(data,index) # pylint: disable=protected-access # noqa: E501 64 | return index 65 | 66 | def toBytes(self)->bytes: 67 | """ 68 | encode this object to a byte array 69 | """ 70 | if self.brush is None: 71 | return bytes() 72 | io=IO() 73 | io.addBytes(self.brush.toBytes()) 74 | io.addBytes(self.pattern.toBytes()) 75 | return io.data 76 | 77 | def save(self, 78 | toFilename:typing.Union[None,str,typing.BinaryIO]=None, 79 | toExtension:typing.Optional[str]=None 80 | )->None: 81 | """ 82 | save this gimp image to a file 83 | """ 84 | f=None 85 | if toFilename is None: 86 | if self.filename is None: 87 | self.filename='untitled.gpb' 88 | toFilename=self.filename 89 | elif isinstance(toFilename,str): 90 | self.filename=toFilename 91 | else: 92 | f=toFilename 93 | toFilename=toFilename.name 94 | self.filename=toFilename 95 | if toExtension is None: 96 | toExtension=toFilename.rsplit('.',1)[-1] 97 | else: 98 | toFilename=toFilename.rsplit('.',1)[0] 99 | toFilename=f'{toFilename}.{toExtension}' 100 | if toExtension=='gpb': 101 | if f is None: 102 | f=open(toFilename,'wb') 103 | f.write(self.toBytes()) 104 | elif toExtension=='vbr': 105 | # TODO: convert between brush types! 106 | raise NotImplementedError() 107 | else: 108 | # save as an image 109 | image=self.brush.image 110 | if image is None: 111 | image=self.pattern.image 112 | if image is not None: 113 | image.save(toFilename) 114 | 115 | def __repr__(self,indent:str='')->str: 116 | """ 117 | Get a textual representation of this object 118 | """ 119 | ret=[] 120 | if self.filename is not None: 121 | ret.append('Filename: '+self.filename) 122 | ret.append(self.brush.__repr__(indent+'\t')) 123 | ret.append(self.pattern.__repr__(indent+'\t')) 124 | return ('\n'+indent).join(ret) 125 | 126 | 127 | def cmdline(args:typing.Iterable[str])->int: 128 | """ 129 | Run the command line 130 | 131 | :param args: command line arguments (WITHOUT the filename) 132 | """ 133 | printhelp=False 134 | if not args: 135 | printhelp=True 136 | else: 137 | g=None 138 | for arg in args: 139 | if arg.startswith('-'): 140 | kv=[a.strip() for a in arg.split('=',1)] 141 | if kv[0] in ['-h','--help']: 142 | printhelp=True 143 | elif kv[0]=='--dump': 144 | print(g) 145 | else: 146 | print(f'ERR: unknown argument "{arg}"') 147 | else: 148 | g=GimpGpbBrush(arg) 149 | if printhelp: 150 | print('Usage:') 151 | print(' gimpGpbBrush.py file.xcf [options]') 152 | print('Options:') 153 | print(' -h, --help ............ this help screen') 154 | print(' --dump ................ dump info about this file') 155 | print(' --register ............ register this extension') 156 | return -1 157 | return 0 158 | 159 | 160 | if __name__=='__main__': 161 | import sys 162 | cmdline(sys.argv[1:]) 163 | -------------------------------------------------------------------------------- /gimpGplPalette.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Pure python implementation of the gimp gpl palette format 5 | """ 6 | import typing 7 | 8 | 9 | class GimpGplPalette: 10 | """ 11 | Pure python implementation of the gimp gpl palette format 12 | 13 | Format: 14 | name: GimpGplPalette 15 | description: Gimp color palette 16 | guid: {45129576-6728-4967-8888-6b9082862ca3} 17 | parentNames: [Color] 18 | #mimeTypes: application/jpeg 19 | filenamePatterns: *.gpl 20 | """ 21 | 22 | MAGIC_NUMBER=(0,"GIMP Palette") 23 | 24 | def __init__(self, 25 | filename:typing.Union[None,str,typing.BinaryIO]=None): 26 | """ """ 27 | self.name:str='' 28 | self.columns:int=16 29 | self.colors:typing.List[typing.Tuple[int,int,int]]=[] 30 | self.colorNames:typing.List[typing.Optional[str]]=[] 31 | self.filename:typing.Optional[str]=None 32 | if filename is not None: 33 | self.load(filename) 34 | 35 | def load(self, 36 | filename:typing.Union[str,typing.BinaryIO] 37 | )->None: 38 | """ 39 | load a gimp file 40 | 41 | :param filename: can be a file name or a file-like object 42 | """ 43 | if isinstance(filename,str): 44 | self.filename=filename 45 | f=open(filename,'rb') 46 | data=f.read() 47 | f.close() 48 | else: 49 | self.filename=filename.name 50 | data=filename.read() 51 | self._decode_(data) 52 | 53 | def _decode_(self, 54 | data:typing.Union[str,bytes,typing.List[str]], 55 | index:int=0 56 | )->None: 57 | """ 58 | decode a byte buffer 59 | 60 | :param data: data buffer to decode 61 | :param index: index within the buffer to start at 62 | """ 63 | if isinstance(data,bytes): 64 | data=data.decode('utf-8') 65 | if isinstance(data,str): 66 | data=[s.strip() for s in data.split('\n')] 67 | if index!=0: 68 | data=data[index:] 69 | if data[0]!="GIMP Palette": 70 | raise Exception('File format error. Magic value mismatch.') 71 | self.name=data[1].split(':',1)[-1].lstrip() 72 | self.columns=int(data[2].split(':',1)[-1].lstrip()) 73 | if data[3]!="#": 74 | raise Exception('File format error. Separtor missing.') 75 | for line in data[4:]: 76 | lineArry=line.split(None,4) 77 | if len(lineArry)<3: 78 | continue 79 | self.colors.append( 80 | (int(lineArry[0]),int(lineArry[1]),int(lineArry[2]))) 81 | if len(lineArry)>3: 82 | self.colorNames.append(' '.join(lineArry[3:])) 83 | else: 84 | self.colorNames.append(None) 85 | 86 | def toBytes(self)->bytes: 87 | """ 88 | encode to a raw data stream 89 | """ 90 | data=[] 91 | data.append("GIMP Palette") 92 | data.append(f'Name: {self.name}') 93 | data.append(f'Columns: {self.columns}') 94 | data.append("#") 95 | for i,color in enumerate(self.colors): 96 | colorName=self.colorNames[i] 97 | line=' '.join([str(c).rjust(3) for c in color]) 98 | if colorName is not None: 99 | line=line+'\t'+colorName 100 | data.append(line) 101 | return ('\n'.join(data)+'\n').encode('utf-8') 102 | 103 | def save(self, 104 | toFilename:typing.Union[None,str,typing.BinaryIO]=None, 105 | toExtension:typing.Optional[str]=None 106 | )->None: 107 | """ 108 | save this gimp image to a file 109 | """ 110 | f=None 111 | if toFilename is None: 112 | if self.filename is None: 113 | self.filename='untitled.gpl' 114 | toFilename=self.filename 115 | elif isinstance(toFilename,str): 116 | self.filename=toFilename 117 | else: 118 | f=toFilename 119 | toFilename=toFilename.name 120 | self.filename=toFilename 121 | if toExtension is None: 122 | if toFilename is not None: 123 | ext=toFilename.rsplit('.',1) 124 | if len(ext)>1: 125 | toExtension=ext[-1] 126 | else: 127 | toExtension=None 128 | if f is None: 129 | f=open(toFilename,'wb') 130 | f.write(self.toBytes()) 131 | 132 | def __repr__(self,indent:str='')->str: 133 | """ 134 | Get a textual representation of this object 135 | """ 136 | ret=[] 137 | if self.filename is not None: 138 | ret.append('Filename: '+self.filename) 139 | ret.append('Name: '+str(self.name)) 140 | ret.append('Columns: '+str(self.columns)) 141 | ret.append('Colors:') 142 | for i,color in enumerate(self.colors): 143 | colorName=self.colorNames[i] 144 | line='(%d,%d,%d)'%(color[0],color[1],color[2]) 145 | if colorName is not None: 146 | line=line+' '+colorName 147 | return indent+(('\n'+indent).join(ret)) 148 | 149 | def __eq__(self,other:typing.Any)->bool: 150 | """ 151 | perform a comparison 152 | """ 153 | if other.name!=self.name: 154 | return False 155 | if other.columns!=self.columns: 156 | return False 157 | if len(self.colors)!=len(other.colors): 158 | return False 159 | for i,c in enumerate(self.colors): 160 | if c!=other.colors[i]: 161 | return False 162 | if self.colorNames[i]!=other.colorNames[i]: 163 | return False 164 | return True 165 | 166 | 167 | def cmdline(args:typing.Iterable[str])->int: 168 | """ 169 | Run the command line 170 | 171 | :param args: command line arguments (WITHOUT the filename) 172 | """ 173 | printhelp=False 174 | if not args: 175 | printhelp=True 176 | else: 177 | g=None 178 | for arg in args: 179 | if arg.startswith('-'): 180 | kv=[a.strip() for a in arg.split('=',1)] 181 | if kv[0] in ('-h','--help'): 182 | printhelp=True 183 | elif kv[0]=='--dump': 184 | print(g) 185 | else: 186 | print(f'ERR: unknown argument "{arg}"') 187 | else: 188 | g=GimpGplPalette(arg) 189 | if printhelp: 190 | print('Usage:') 191 | print(' gimpGplPalette.py palette.gpl [options]') 192 | print('Options:') 193 | print(' -h, --help ............ this help screen') 194 | print(' --dump ................ dump info about this file') 195 | print(' --register ............ register this extension') 196 | return -1 197 | return 0 198 | 199 | 200 | if __name__=='__main__': 201 | import sys 202 | cmdline(sys.argv[1:]) 203 | -------------------------------------------------------------------------------- /gimpGtpToolPreset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Pure python implementation of the gimp gtp tool preset format 5 | """ 6 | import typing 7 | 8 | 9 | class ParenFileValue: 10 | """ 11 | A parentheses-based file format 12 | (possibly "scheme" language?) 13 | """ 14 | 15 | def __init__(self)->None: 16 | self.name:typing.Optional[str]=None 17 | self.values:typing.List[typing.Union[str,float,bool]]=[] 18 | 19 | def _addValue(self,bufArray:typing.List[str])->None: 20 | if self.name is None: # first value is the name 21 | self.name=bufArray[0] 22 | if bufArray: 23 | bufStr=''.join(bufArray) 24 | if bufStr in ('yes','no'): # boolean 25 | self.values.append(bufStr=='yes') 26 | elif bufStr[0].isdigit() or bufStr[0] in ('-','.'): 27 | if bufStr[0]=='.': 28 | bufStr='0'+bufStr 29 | self.values.append(float(bufStr)) 30 | elif bufStr[0]=='"': 31 | self.values.append(bufStr[1:-1]) 32 | else: 33 | msg=f'Unknown value "{bufArray}" while setting "{self.name}"' 34 | raise Exception(msg) 35 | 36 | def __repr__(self,indent:str='')->str: 37 | """ 38 | Get a textual representation of this object 39 | """ 40 | ret=[] 41 | ret.append('Value Type: '+str(self.name)) 42 | for v in self.values: 43 | if isinstance(v,ParenFileValue): 44 | ret.append(v.__repr__(indent+'\t')) 45 | else: 46 | ret.append(str(v)) 47 | return ('\n'+indent).join(ret) 48 | 49 | 50 | def parenFileDecode( 51 | data:typing.Union[str,bytes], 52 | index:int=0 53 | )->typing.Tuple[int,typing.Iterable[ParenFileValue]]: 54 | """ 55 | Decode a parentheses-based file format 56 | (possibly "scheme" language?) 57 | """ 58 | values=[] 59 | if isinstance(data,bytes): 60 | data=data.decode('utf-8') 61 | maxIndex=len(data) 62 | def pDec(data,index=0): 63 | """ 64 | helper routine to parse a single value 65 | """ 66 | pval=ParenFileValue() 67 | ws=[' ','\t','\r','\n'] 68 | if index==0: 69 | while index=maxIndex: 83 | break 84 | building.append(c) 85 | c=data[index] 86 | index+=1 87 | pval.name=''.join(building) 88 | building=[] 89 | while index=maxIndex: 108 | break 109 | c=data[index] 110 | building.append(c) 111 | if c=='"': 112 | break 113 | pval._addValue(building) # pylint: disable=protected-access 114 | building=[] 115 | index+=1 116 | else: 117 | building.append(c) 118 | index+=1 119 | return index,pval 120 | while indexstr: 127 | """ 128 | encode a values tree to a buffer 129 | """ 130 | ret=[] 131 | ret.append('# GIMP tool preset file') 132 | ret.append('') 133 | for val in values: 134 | if val.name is not None: 135 | ret.append(str(val)) 136 | ret.append('') 137 | ret.append('# end of GIMP tool preset file') 138 | ret.append('') 139 | return '\n'.join(ret) 140 | 141 | 142 | class GimpGtpToolPreset: 143 | """ 144 | Pure python implementation of the gimp gtp tool preset format 145 | 146 | Format: 147 | name: GimpGtpToolPreset 148 | description: Gimp tool preset 149 | guid: {45129576-6728-4967-8888-6b9082862ca4} 150 | #parentNames: 151 | #mimeTypes: application/jpeg 152 | filenamePatterns: *.gtp 153 | """ 154 | 155 | def __init__(self, 156 | filename:typing.Union[None,str,typing.BinaryIO]=None): 157 | """ """ 158 | self.values:typing.List[ParenFileValue]=[] 159 | self.filename:typing.Optional[str]=None 160 | if filename is not None: 161 | self.load(filename) 162 | 163 | def load(self,filename:typing.Union[str,typing.BinaryIO])->None: 164 | """ 165 | load a gimp file 166 | 167 | :param filename: can be a file name or a file-like object 168 | """ 169 | if isinstance(filename,str): 170 | self.filename=filename 171 | f=open(filename,'rb') 172 | data=f.read() 173 | f.close() 174 | else: 175 | self.filename=filename.name 176 | data=filename.read() 177 | self._decode_(data) 178 | 179 | def _decode_(self,data:typing.Union[str,bytes],index:int=0)->int: 180 | """ 181 | decode a byte buffer 182 | 183 | :param data: data buffer to decode 184 | :param index: index within the buffer to start at 185 | """ 186 | index,values=parenFileDecode(data,index) 187 | self.values=list(values) 188 | return index 189 | 190 | def toBytes(self)->bytes: 191 | """ 192 | encode to bytes 193 | """ 194 | return parenFileEncode(self.values).encode('utf-8') 195 | 196 | def save(self, 197 | toFilename:typing.Union[str,None,typing.BinaryIO]=None, 198 | toExtension:typing.Optional[str]=None 199 | )->None: 200 | """ 201 | save this gimp tool preset to a file 202 | """ 203 | f=None 204 | if toFilename is None: 205 | if self.filename is None: 206 | self.filename='untitled.gtp' 207 | toFilename=self.filename 208 | elif isinstance(toFilename,str): 209 | self.filename=toFilename 210 | else: 211 | f=toFilename 212 | toFilename=f.name 213 | self.filename=toFilename 214 | if toExtension is None: 215 | if toFilename is not None: 216 | ext=toFilename.rsplit('.',1) 217 | if len(ext)>1: 218 | toExtension=ext[-1] 219 | else: 220 | toExtension=None 221 | if f is None: 222 | f=open(toFilename,'wb') 223 | f.write(self.toBytes()) 224 | 225 | def __repr__(self,indent:str='')->str: 226 | """ 227 | Get a textual representation of this object 228 | """ 229 | ret=[] 230 | for v in self.values: 231 | ret.append(v.__repr__(indent+'\t')) 232 | return '\n'.join(ret) 233 | 234 | 235 | def cmdline(args:typing.Iterable[str])->int: 236 | """ 237 | Run the command line 238 | 239 | :param args: command line arguments (WITHOUT the filename) 240 | """ 241 | printhelp=False 242 | if not args: 243 | printhelp=True 244 | else: 245 | g=None 246 | for arg in args: 247 | if arg.startswith('-'): 248 | kv=[a.strip() for a in arg.split('=',1)] 249 | if kv[0] in ('-h','--help'): 250 | printhelp=True 251 | elif kv[0]=='--dump': 252 | print(g) 253 | else: 254 | print(f'ERR:unknown argument "{arg}"') 255 | else: 256 | g=GimpGtpToolPreset(arg) 257 | if printhelp: 258 | print('Usage:') 259 | print(' gimpGtpToolPreset.py file.xcf [options]') 260 | print('Options:') 261 | print(' -h, --help ............ this help screen') 262 | print(' --dump ................ dump info about this file') 263 | print(' --register ............ register this extension') 264 | return -1 265 | return 0 266 | 267 | 268 | if __name__=='__main__': 269 | import sys 270 | cmdline(sys.argv[1:]) 271 | -------------------------------------------------------------------------------- /gimpParasites.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Parasites are arbitrary (meta)data strings that can be attached 5 | to a document tree item 6 | 7 | They are used to store things like last-used plugin settings, 8 | gamma adjuetments, etc. 9 | 10 | Format of known parasites: 11 | https://gitlab.gnome.org/GNOME/gimp/blob/master/devel-docs/parasites.txt 12 | """ 13 | import typing 14 | from gimpFormats.binaryIO import IO 15 | 16 | 17 | #TODO: how to best use these for our puproses?? 18 | KNOWN_DOCUMENT_PARASITES=[ 19 | "jpeg-save-defaults", 20 | "png-save-defaults", 21 | "/_fu_data", 22 | "exif-orientation-rotate"] 23 | KNOWN_IMAGE_PARASITES=[ 24 | "gimp-comment", 25 | "gimp-brush-name", 26 | "gimp-brush-pipe-name", 27 | "gimp-brush-pipe-parameters", 28 | "gimp-image-grid", 29 | "gimp-pattern-name", 30 | "tiff-save-options", 31 | "jpeg-save-options", 32 | "jpeg-exif-data", 33 | "jpeg-original-settings", 34 | "gamma", 35 | "chromaticity", 36 | "rendering-intent", 37 | "hot-spot", 38 | "exif-data", 39 | "gimp-metadata", 40 | "icc-profile", 41 | "icc-profile-name", 42 | "decompose-data", 43 | "print-settings", 44 | "print-page-setup", 45 | "dcm/XXXX-XXXX-AA"] 46 | KNOWN_LAYER_PARASITES=[ 47 | "gimp-text-layer", 48 | "gfig"] 49 | 50 | 51 | class GimpParasite: 52 | """ 53 | Parasites are arbitrary (meta)data strings that can be attached to 54 | a document tree item 55 | 56 | They are used to store things like last-used plugin settings, 57 | gamma adjuetments, etc. 58 | 59 | Format of known parasites: 60 | https://gitlab.gnome.org/GNOME/gimp/blob/master/devel-docs/parasites.txt 61 | """ 62 | 63 | def __init__(self)->None: 64 | self.name:str='' 65 | self.flags:int=0 66 | self.data:typing.Optional[bytes]=None 67 | 68 | def fromBytes(self,data,index=0): 69 | """ 70 | decode a byte buffer 71 | 72 | :param data: data buffer to decode 73 | :param index: index within the buffer to start at 74 | """ 75 | io=IO(data,index) 76 | self.name=io.sz754 77 | self.flags=io.u32 78 | dataLength=io.u32 79 | self.data=io.getBytes(dataLength) 80 | return io.index 81 | 82 | def toBytes(self): 83 | """ 84 | decode a byte buffer 85 | 86 | :param data: data buffer to decode 87 | :param index: index within the buffer to start at 88 | """ 89 | io=IO() 90 | io.sz754=self.name 91 | io.u32=self.flags 92 | io.u32=len(self.data) 93 | io.addBytes(self.data) 94 | return io.data 95 | 96 | def __repr__(self,indent=''): 97 | """ 98 | Get a textual representation of this object 99 | """ 100 | ret=[] 101 | ret.append('Name: '+str(self.name)) 102 | ret.append('Flags: '+str(self.flags)) 103 | ret.append('Data Len: '+str(len(self.data))) 104 | return indent+(('\n'+indent).join(ret)) 105 | -------------------------------------------------------------------------------- /gimpPatPattern.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Pure python implementation of a gimp pattern file 5 | """ 6 | import typing 7 | import PIL.Image 8 | from gimpFormats.binaryIO import IO 9 | 10 | 11 | class GimpPatPattern: 12 | """ 13 | Pure python implementation of a gimp pattern file 14 | 15 | See: 16 | https://gitlab.gnome.org/GNOME/gimp/blob/master/devel-docs/pat.txt 17 | 18 | Format: 19 | name: GimpPatPattern 20 | description: Gimp pattern 21 | guid: {45129576-6728-4967-8888-6b9082862ca5} 22 | parentNames: Image 23 | #mimeTypes: application/jpeg 24 | filenamePatterns: *.pat 25 | """ 26 | 27 | MAGIC_NUMBER=(0,'GPAT') 28 | 29 | COLOR_MODES=(None,'L','LA','RGB','RGBA') 30 | 31 | def __init__(self, 32 | filename:typing.Union[None,str,typing.BinaryIO]=None): 33 | """ """ 34 | self.filename:typing.Optional[str]=None 35 | self.version:float=1 36 | self.width:int=0 37 | self.height:int=0 38 | self.bpp:int=4 39 | self.mode:typing.Optional[str]=self.COLOR_MODES[self.bpp] 40 | self.name:str='' 41 | self._rawImage:typing.Optional[bytes]=None 42 | self._image:typing.Optional[PIL.Image.Image]=None 43 | if filename is not None: 44 | self.load(filename) 45 | 46 | def load(self,filename:typing.Union[str,typing.BinaryIO])->None: 47 | """ 48 | load a gimp file 49 | 50 | :param filename: can be a file name or a file-like object 51 | """ 52 | if isinstance(filename,str): 53 | self.filename=filename 54 | f=open(filename,'rb') 55 | data=f.read() 56 | f.close() 57 | else: 58 | self.filename=filename.name 59 | data=filename.read() 60 | self._decode_(data) 61 | 62 | def _decode_(self,data:bytes,index:int=0)->int: 63 | """ 64 | decode a byte buffer 65 | 66 | :param data: data buffer to decode 67 | :param index: index within the buffer to start at 68 | """ 69 | io=IO(data,index) 70 | headerSize=io.u32 71 | self.version=io.u32 72 | self.width=io.u32 73 | self.height=io.u32 74 | self.bpp=io.u32 75 | self.mode=self.COLOR_MODES[self.bpp] 76 | magic=io.getBytes(4) 77 | if magic.decode('ascii')!='GPAT': 78 | raise Exception('File format error. Magic value mismatch.') 79 | nameLen=headerSize-io.index 80 | self.name=io.getBytes(nameLen).decode('UTF-8') 81 | self._rawImage=io.getBytes(self.width*self.height*self.bpp) 82 | self._image=None 83 | return io.index-index 84 | 85 | def toBytes(self)->bytes: 86 | """ 87 | encode to a byte buffer 88 | """ 89 | if self.image is None: 90 | return bytes() 91 | io=IO() 92 | io.u32=24+len(self.name) 93 | io.u32=self.version 94 | io.u32=self.width 95 | io.u32=self.height 96 | mode=self.image.mode 97 | if mode is None: 98 | mode='L' 99 | io.u32=len(mode) 100 | io.addBytes('GPAT') 101 | io.addBytes(self.name.encode('utf-8')) 102 | if self._rawImage is None: 103 | rawImage=self.image.tobytes(encoder_name='raw') 104 | else: 105 | rawImage=self._rawImage 106 | io.addBytes(rawImage) 107 | return io.data 108 | 109 | @property 110 | def size(self)->typing.Tuple[int,int]: 111 | """ 112 | the size of the pattern 113 | """ 114 | return (self.width,self.height) 115 | 116 | @property 117 | def image(self)->typing.Optional[PIL.Image.Image]: 118 | """ 119 | get a final, compiled image 120 | """ 121 | if self._image is None: 122 | if self._rawImage is None: 123 | return None 124 | raw=bytes(self._rawImage) 125 | mode=self.mode 126 | if mode is None: 127 | mode='L' 128 | self._image=PIL.Image.frombytes( 129 | mode,self.size,raw,decoder_name='raw') 130 | return self._image 131 | @image.setter 132 | def image(self,image:PIL.Image.Image): 133 | self._image=image 134 | self._rawImage=None 135 | 136 | def save(self, 137 | toFilename:typing.Optional[str]=None, 138 | toExtension:typing.Optional[str]=None 139 | )->None: 140 | """ 141 | save this gimp image to a file 142 | """ 143 | asImage=False 144 | f=None 145 | if toFilename is None: 146 | if self.filename is None: 147 | self.filename='untitled.pat' 148 | toFilename=self.filename 149 | elif isinstance(toFilename,str): 150 | self.filename=str(toFilename) 151 | else: 152 | f=toFilename 153 | toFilename=toFilename.name 154 | self.filename=toFilename 155 | if toExtension is None: 156 | if toFilename is not None: 157 | ext=toFilename.rsplit('.',1) 158 | if len(ext)>1: 159 | toExtension=ext[-1] 160 | else: 161 | toExtension=None 162 | if toExtension is not None and toExtension!='pat': 163 | asImage=True 164 | if asImage: 165 | if self.image is not None: 166 | self.image.save(toFilename) 167 | else: 168 | if f is None: 169 | f=open(toFilename,'wb') 170 | f.write(self.toBytes()) 171 | 172 | def __repr__(self,indent:str='')->str: 173 | """ 174 | Get a textual representation of this object 175 | """ 176 | ret=[] 177 | if self.filename is not None: 178 | ret.append('Filename: '+self.filename) 179 | ret.append('Name: '+str(self.name)) 180 | ret.append('Version: '+str(self.version)) 181 | ret.append('Size: '+str(self.width)+' x '+str(self.height)) 182 | ret.append('BPP: '+str(self.bpp)) 183 | ret.append('Mode: '+str(self.mode)) 184 | return '\n'.join(ret) 185 | 186 | 187 | def cmdline(args:typing.Iterable[str])->int: 188 | """ 189 | Run the command line 190 | 191 | :param args: command line arguments (WITHOUT the filename) 192 | """ 193 | printhelp=False 194 | if not args: 195 | printhelp=True 196 | else: 197 | g=None 198 | for arg in args: 199 | if arg.startswith('-'): 200 | kv=[a.strip() for a in arg.split('=',1)] 201 | if kv[0] in ('-h','--help'): 202 | printhelp=True 203 | elif kv[0]=='--dump': 204 | print(g) 205 | elif kv[0]=='--show': 206 | if g is None: 207 | print('ERR: No pattern to show') 208 | else: 209 | g.image.show() 210 | elif kv[0]=='--save': 211 | if g is None: 212 | print('ERR: No pattern to save') 213 | else: 214 | g.image.save(kv[1]) 215 | else: 216 | print(f'ERR: unknown argument "{arg}"') 217 | else: 218 | g=GimpPatPattern(arg) 219 | if printhelp: 220 | print('Usage:') 221 | print(' gimpPatPattern.py file.xcf [options]') 222 | print('Options:') 223 | print(' -h, --help ............ this help screen') 224 | print(' --dump ................ dump info about this file') 225 | print(' --show ................ show the pattern image') 226 | print(' --save=out.jpg ........ save out the pattern image') 227 | print(' --register ............ register this extension') 228 | return -1 229 | return 0 230 | 231 | 232 | if __name__=='__main__': 233 | import sys 234 | cmdline(sys.argv[1:]) 235 | -------------------------------------------------------------------------------- /gimpVbrBrush.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding:utf-8 -*- 3 | """ 4 | Pure python implementation of the gimp vbr brush format 5 | """ 6 | import typing 7 | from enum import Enum 8 | import PIL.Image 9 | 10 | 11 | BRUSH_SHAPES=Enum('BRUSH_SHAPES',start=0,names=( 12 | "circle","square","diamond" 13 | )) 14 | 15 | class GimpVbrBrush: 16 | """ 17 | Pure python implementation of the gimp vbr brush format 18 | 19 | See: 20 | https://gitlab.gnome.org/GNOME/gimp/blob/master/devel-docs/vbr.txt 21 | 22 | Format: 23 | name:GimpVbrBrush 24 | description:Gimp brush 25 | guid:{45129576-6728-4967-8888-6b9082862ca6} 26 | parentNames:Image 27 | #mimeTypes:application/jpeg 28 | filenamePatterns:*.vbr 29 | """ 30 | 31 | MAGIC_NUMBER:typing.Tuple[int,str]=(0,'GIMP-VBR') 32 | 33 | def __init__(self, 34 | filename:typing.Union[None,str,typing.BinaryIO]=None 35 | )->None: 36 | """ """ 37 | self.version:float=1.0 38 | self.name:str='' 39 | self.spacing:float=0 40 | self.radius:float=50 41 | self.hardness:float=1 42 | self.aspectRatio:float=1 43 | self.angle:float=0 44 | self.brushShape:typing.Optional[BRUSH_SHAPES]=None 45 | self.spikes:typing.Optional[float]=None 46 | self.filename:typing.Optional[str]=None 47 | if filename is not None: 48 | self.load(filename) 49 | 50 | def load(self,filename:typing.Union[str,typing.BinaryIO])->None: 51 | """ 52 | load a gimp file 53 | 54 | :param filename:can be a file name or a file-like object 55 | """ 56 | data:bytes 57 | if isinstance(filename,str): 58 | self.filename=filename 59 | f=open(filename,'rb') 60 | data=f.read() 61 | f.close() 62 | else: 63 | self.filename=filename.name 64 | data=filename.read() 65 | self._decode_(data) 66 | 67 | @property 68 | def image(self)->PIL.Image.Image: 69 | """ 70 | this parametric brush converted to a useable PIL image 71 | """ 72 | raise NotImplementedError() # TODO: 73 | 74 | def _decode_(self, 75 | data:typing.Union[str,bytes,typing.List[str]], 76 | index:int=0 77 | )->None: 78 | """ 79 | decode a byte buffer 80 | 81 | :param data:data buffer to decode 82 | :param index:index within the buffer to start at 83 | """ 84 | if isinstance(data,bytes): 85 | data=data.decode('utf-8') 86 | if isinstance(data,str): 87 | data=[s.strip() for s in data.split('\n')] 88 | if index!=0: 89 | data=data[index:] 90 | if data[0]!="GIMP-VBR": 91 | raise Exception('File format error. Magic value mismatch.') 92 | self.version=float(data[1]) 93 | if self.version==1.0: 94 | self.name=data[2] # max len 255 bytes 95 | self.spacing=float(data[3]) 96 | self.radius=float(data[4]) 97 | self.hardness=float(data[5]) 98 | self.aspectRatio=float(data[6]) 99 | self.angle=float(data[7]) 100 | elif self.version==1.5: 101 | self.name=data[2] # max len 255 bytes 102 | self.brushShape=BRUSH_SHAPES(data[3]) 103 | self.spacing=float(data[4]) 104 | self.radius=float(data[5]) 105 | self.spikes=float(data[6]) 106 | self.hardness=float(data[7]) 107 | self.aspectRatio=float(data[8]) 108 | self.angle=float(data[9]) 109 | else: 110 | raise Exception('Unknown version '+str(self.version)) 111 | 112 | def toBytes(self)->bytes: 113 | """ 114 | encode to a raw data stream 115 | """ 116 | data=[] 117 | data.append("GIMP-VBR") 118 | data.append(str(self.version)) 119 | if self.version==1.0: 120 | data.append(str(self.name)) 121 | data.append(str(self.spacing)) 122 | data.append(str(self.radius)) 123 | data.append(str(self.hardness)) 124 | data.append(str(self.aspectRatio)) 125 | data.append(str(self.angle)) 126 | elif self.version==1.5: 127 | data.append(str(self.name)) 128 | data.append(str(self.brushShape)) 129 | data.append(str(self.spacing)) 130 | data.append(str(self.radius)) 131 | data.append(str(self.spikes)) 132 | data.append(str(self.hardness)) 133 | data.append(str(self.aspectRatio)) 134 | data.append(str(self.angle)) 135 | return ('\n'.join(data)+'\n').encode('utf-8') 136 | 137 | def save(self, 138 | toFilename:typing.Union[None,str,typing.BinaryIO]=None, 139 | toExtension:typing.Optional[str]=None 140 | )->None: 141 | """ 142 | save this gimp image to a file 143 | """ 144 | asImage=False 145 | f=None 146 | if toFilename is None: 147 | if self.filename is None: 148 | self.filename='Untitled.vbr' 149 | toFilename=self.filename 150 | elif isinstance(toFilename,str): 151 | self.filename=toFilename 152 | else: 153 | f=toFilename 154 | toFilename=toFilename.name 155 | self.filename=toFilename 156 | if toExtension is None: 157 | if toFilename is not None: 158 | ext=toFilename.rsplit('.',1) 159 | if len(ext)>1: 160 | toExtension=ext[-1] 161 | else: 162 | toExtension=None 163 | if toExtension is not None and toExtension!='vbr': 164 | if toExtension=='gpb': 165 | # TODO: convert between brush types! 166 | raise NotImplementedError() 167 | asImage=True 168 | if asImage: 169 | self.image.save(toFilename) 170 | else: 171 | if f is None: 172 | f=open(toFilename,'wb') 173 | f.write(self.toBytes()) 174 | 175 | def __repr__(self,indent:str='')->str: 176 | """ 177 | Get a textual representation of this object 178 | """ 179 | ret=[] 180 | if self.filename is not None: 181 | ret.append('Filename:'+self.filename) 182 | ret.append('Name:'+str(self.name)) 183 | ret.append('Version:'+str(self.version)) 184 | ret.append('Spacing:'+str(self.spacing)) 185 | ret.append('Radius:'+str(self.radius)) 186 | ret.append('Hardness:'+str(self.hardness)) 187 | ret.append('Aspect ratio:'+str(self.aspectRatio)) 188 | ret.append('Angle:'+str(self.angle)) 189 | ret.append('Brush Shape:'+str(self.brushShape)) 190 | ret.append('Spikes:'+str(self.spikes)) 191 | return ('\n'+indent).join(ret) 192 | 193 | def __eq__(self,other:typing.Any)->bool: 194 | """ 195 | perform a comparison 196 | """ 197 | if not isinstance(other,GimpVbrBrush): 198 | return False 199 | if other.name!=self.name: 200 | return False 201 | if other.version!=self.version: 202 | return False 203 | if other.spacing!=self.spacing: 204 | return False 205 | if other.radius!=self.radius: 206 | return False 207 | if other.hardness!=self.hardness: 208 | return False 209 | if other.aspectRatio!=self.aspectRatio: 210 | return False 211 | if other.angle!=self.angle: 212 | return False 213 | if other.brushShape!=self.brushShape: 214 | return False 215 | if other.spikes!=self.spikes: 216 | return False 217 | return True 218 | 219 | 220 | def cmdline(args:typing.Iterable[str])->int: 221 | """ 222 | Run the command line 223 | 224 | :param args:command line arguments (WITHOUT the filename) 225 | """ 226 | printhelp=False 227 | if not args: 228 | printhelp=True 229 | else: 230 | g=None 231 | for arg in args: 232 | if arg.startswith('-'): 233 | kv=[a.strip() for a in arg.split('=',1)] 234 | if kv[0] in ('-h','--help'): 235 | printhelp=True 236 | elif kv[0]=='--dump': 237 | print(g) 238 | else: 239 | print(f'ERR: unknown argument "{arg}"') 240 | else: 241 | g=GimpVbrBrush(arg) 242 | if printhelp: 243 | print('Usage:') 244 | print(' gimpVbrBrush.py file.xcf [options]') 245 | print('Options:') 246 | print(' -h, --help ............ this help screen') 247 | print(' --dump ................ dump info about this file') 248 | print(' --register ............ register this extension') 249 | return -1 250 | return 0 251 | 252 | 253 | if __name__=='__main__': 254 | import sys 255 | cmdline(sys.argv[1:]) 256 | -------------------------------------------------------------------------------- /gimpVectors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Stuff related to vectors/paths within a gimp document 5 | """ 6 | import typing 7 | from gimpFormats.gimpIOBase import GimpIOBase 8 | from gimpFormats.binaryIO import IO 9 | from gimpFormats.gimpParasites import GimpParasite 10 | 11 | 12 | class GimpVector(GimpIOBase): 13 | """ 14 | A gimp brush stroke vector 15 | """ 16 | 17 | def __init__(self, 18 | parent:typing.Optional[GimpIOBase]): 19 | """ """ 20 | GimpIOBase.__init__(self,parent) 21 | self.name:str='' 22 | self.visible:bool=True 23 | self.linked:bool=False 24 | self.parasites:typing.List[GimpParasite]=[] 25 | self.strokes:typing.List[GimpStroke]=[] 26 | 27 | @property 28 | def svgPath(self)->str: 29 | """ 30 | this vector converted to an svg path string 31 | """ 32 | svg=[] 33 | for stroke in self.strokes: 34 | svg.append(stroke.svgPath) 35 | print('STROKES=%d %s'%(len(self.strokes),svg)) 36 | return ' '.join(svg) 37 | @svgPath.setter 38 | def svgPath(self,svgPath:str): 39 | raise NotImplementedError() 40 | 41 | def fromBytes(self,data,index=0): 42 | """ 43 | decode a byte buffer 44 | 45 | :param data: data buffer to decode 46 | :param index: index within the buffer to start at 47 | """ 48 | io=IO(data,index,boolSize=32) 49 | print(io.data[0:50]) 50 | self.name=io.sz754 51 | self.uniqueId=io.u32 52 | self.visible=io.bool 53 | self.linked=io.bool 54 | numParasites=io.u32 55 | numStrokes=io.u32 56 | for _ in range(numParasites): 57 | p=GimpParasite() 58 | io.index=p.fromBytes(io.data,io.index) 59 | self.parasites.append(p) 60 | for _ in range(numStrokes): 61 | gs=GimpStroke(self) 62 | io.index=gs.fromBytes(io.data,io.index) 63 | self.strokes.append(p) 64 | return io.index 65 | 66 | def toBytes(self): 67 | """ 68 | encode to binary data 69 | """ 70 | io=IO(boolSize=32) 71 | io.sz754=self.name 72 | io.u32=self.uniqueId 73 | io.bool=self.visible 74 | io.bool=self.linked 75 | io.u32=len(self.parasites) 76 | io.u32=len(self.strokes) 77 | for p in self.parasites: 78 | io.addBytes(p.toBytes()) 79 | for gs in self.strokes: 80 | io.addBytes(gs.toBytes()) 81 | return io.data 82 | 83 | def __repr__(self,indent=''): 84 | """ 85 | Get a textual representation of this object 86 | """ 87 | ret=[] 88 | ret.append('Name: '+str(self.name)) 89 | ret.append('Unique ID (tattoo): '+str(self.uniqueId)) 90 | ret.append('Visible: '+str(self.visible)) 91 | ret.append('Linked: '+str(self.linked)) 92 | if self.parasites: 93 | ret.append('Parasites: ') 94 | for item in self.parasites: 95 | ret.append(item.__repr__(indent+'\t')) 96 | if self.strokes: 97 | ret.append('Strokes: ') 98 | for item in self.strokes: 99 | ret.append(item.__repr__(indent+'\t')) 100 | return indent+(('\n'+indent).join(ret)) 101 | 102 | 103 | class GimpStroke(GimpIOBase): 104 | """ 105 | A single stroke within a vector 106 | """ 107 | 108 | STROKE_TYPES=['None','Bezier'] 109 | 110 | def __init__(self,parent:GimpIOBase): 111 | GimpIOBase.__init__(self,parent) 112 | self.strokeType:int=1 # one of self.STROKE_TYPES 113 | self.closedShape:bool=True 114 | self.points:typing.List[GimpPoint]=[] 115 | self.numFloatsPerPoint:int=1 116 | self.numPoints:int=0 117 | 118 | @property 119 | def svgPath(self)->str: 120 | """ 121 | this vector converted to an svg path string 122 | """ 123 | svg:typing.List[str]=[] 124 | for point in self.points: 125 | if not svg: 126 | # initial move 127 | svg.append('M%d %d'%(point.x,point.y)) 128 | elif self.strokeType==0: 129 | # line 130 | svg.append('L%d %d'%(point.x,point.y)) 131 | elif self.strokeType==1: 132 | # bezier 133 | if point.pointType==0: 134 | # anchor point 135 | svg.append('%d %d Q'%(point.x,point.y)) 136 | else: 137 | # control point 138 | svg.append('%d %d,'%(point.x,point.y)) 139 | else: 140 | raise Exception('Unknown stroke type') 141 | if self.closedShape: 142 | svg.append('Z') 143 | return ' '.join(svg) 144 | 145 | def fromBytes(self,data:bytes,index:int=0): 146 | """ 147 | decode a byte buffer 148 | 149 | :param data: data buffer to decode 150 | :param index: index within the buffer to start at 151 | """ 152 | io=IO(data,index,boolSize=32) 153 | self.strokeType=io.u32 154 | self.closedShape=io.bool 155 | self.numFloatsPerPoint=io.u32 156 | self.numPoints=io.u32 157 | for _ in range(self.numPoints): 158 | gp=GimpPoint(self) 159 | io.index=gp.fromBytes(io.data,io.index,self.numFloatsPerPoint) 160 | self.points.append(gp) 161 | return io.index 162 | 163 | def toBytes(self): 164 | """ 165 | encode to binary data 166 | """ 167 | io=IO(boolSize=32) 168 | io.u32=self.strokeType 169 | io.bool=self.closedShape 170 | io.u32=self.numFloatsPerPoint 171 | io.u32=self.numPoints 172 | for gp in self.points: 173 | io.addBytes(gp.toBytes()) 174 | return io.data 175 | 176 | def __repr__(self,indent=''): 177 | """ 178 | Get a textual representation of this object 179 | """ 180 | ret=[] 181 | ret.append('Stroke Type: '+self.STROKE_TYPES[self.strokeType]) 182 | ret.append('Closed: '+str(self.closedShape)) 183 | ret.append('Points: ') 184 | for point in self.points: 185 | ret.append(point.__repr__(indent+'\t')) 186 | return indent+(('\n'+indent).join(ret)) 187 | 188 | 189 | class GimpPoint(GimpIOBase): 190 | """ 191 | A single point within a stroke 192 | """ 193 | 194 | POINT_TYPES=['Anchor','Bezier control point'] 195 | 196 | def __init__(self,parent:GimpIOBase): 197 | GimpIOBase.__init__(self,parent) 198 | self.x:int=0 199 | self.y:int=0 200 | self.pressure:float=1.0 201 | self.xTilt:float=0.5 202 | self.yTilt:float=0.5 203 | self.wheel:float=0.5 204 | self.pointType:int=0 205 | 206 | def fromBytes(self,data,index=0,numFloatsPerPoint=0): 207 | """ 208 | decode a byte buffer 209 | 210 | :param data: data buffer to decode 211 | :param index: index within the buffer to start at 212 | :param numFloatsPerPoint: required so we know 213 | how many different brush dynamic measurements are 214 | inside each point 215 | """ 216 | io=IO(data,index,boolSize=32) 217 | self.pressure=1.0 218 | self.xTilt=0.5 219 | self.yTilt=0.5 220 | self.wheel=0.5 221 | self.pointType=io.u32 222 | if numFloatsPerPoint<1: 223 | numFloatsPerPoint=(len(io.data)-io.index)/4 224 | self.x=io.float 225 | self.y=io.float 226 | if numFloatsPerPoint>2: 227 | self.pressure=io.float 228 | if numFloatsPerPoint>3: 229 | self.xTilt=io.float 230 | if numFloatsPerPoint>4: 231 | self.yTilt=io.float 232 | if numFloatsPerPoint>5: 233 | self.wheel=io.float 234 | return io.index 235 | 236 | def toBytes(self): 237 | """ 238 | encode to binary data 239 | """ 240 | io=IO(boolSize=32) 241 | io.u32=self.pointType 242 | io.float=self.x 243 | io.float=self.y 244 | if self.pressure is not None: 245 | io.float=self.pressure 246 | if self.xTilt is not None: 247 | io.float=self.xTilt 248 | if self.yTilt is not None: 249 | io.float=self.yTilt 250 | if self.wheel is not None: 251 | io.float=self.wheel 252 | return io.data 253 | 254 | def __repr__(self,indent=''): 255 | """ 256 | Get a textual representation of this object 257 | """ 258 | ret=[] 259 | ret.append(f'Location: ({self.x},{self.y})') 260 | ret.append(f'Pressure: {self.pressure}') 261 | ret.append(f'Location: ({self.xTilt},{self.yTilt})') 262 | ret.append(f'Wheel: {self.wheel}') 263 | return indent+(('\n'+indent).join(ret)) 264 | -------------------------------------------------------------------------------- /install.bat: -------------------------------------------------------------------------------- 1 | cmd /k python setup.py install -------------------------------------------------------------------------------- /notes/GIMP - GIMP Batch Mode.URL: -------------------------------------------------------------------------------- 1 | [InternetShortcut] 2 | URL=https://www.gimp.org/tutorials/Basic_Batch/ 3 | IDList= 4 | HotKey=0 5 | IconFile=C:\Users\kurt\AppData\Local\Mozilla\Firefox\Profiles\oyplzdbp.default\shortcutCache\VMQut1Bucu_uOsPmN31BDw==.ico 6 | IconIndex=0 7 | -------------------------------------------------------------------------------- /notes/Gimp - Write GIMP extensionsplug-insload- and save-handlers in Perl - metacpan.org.URL: -------------------------------------------------------------------------------- 1 | [InternetShortcut] 2 | URL=https://metacpan.org/pod/Gimp 3 | IDList= 4 | HotKey=0 5 | IconFile=C:\Users\Flower\AppData\Local\Mozilla\Firefox\Profiles\iH5zgDjh.default\shortcutCache\z9ebikWS+MfZdHAYJkvBqw==.ico 6 | IconIndex=0 7 | -------------------------------------------------------------------------------- /notes/appxcf · master · GNOME GIMP · GitLab.URL: -------------------------------------------------------------------------------- 1 | [InternetShortcut] 2 | URL=https://gitlab.gnome.org/GNOME/gimp/tree/master/app/xcf 3 | IDList= 4 | HotKey=0 5 | IconFile=C:\Users\kurt\AppData\Local\Mozilla\Firefox\Profiles\9f2hzexm.default\shortcutCache\c+2Jg8beTob0IFSu8Z6WnA==.ico 6 | IconIndex=0 7 | -------------------------------------------------------------------------------- /notes/devel-docsxcf.txt · master · GNOME GIMP · GitLab.URL: -------------------------------------------------------------------------------- 1 | [InternetShortcut] 2 | URL=https://gitlab.gnome.org/GNOME/gimp/blob/master/devel-docs/xcf.txt 3 | IDList= 4 | HotKey=0 5 | IconFile=C:\Users\kurt\AppData\Local\Mozilla\Firefox\Profiles\9f2hzexm.default\shortcutCache\fqfprHn1jMk1koEjyiLQfg==.ico 6 | IconIndex=0 7 | -------------------------------------------------------------------------------- /notes/gimp formats in txt files.URL: -------------------------------------------------------------------------------- 1 | [InternetShortcut] 2 | URL=https://gitlab.gnome.org/GNOME/gimp/tree/master/devel-docs 3 | IDList= 4 | HotKey=0 5 | IconFile=C:\Users\kurt\AppData\Local\Mozilla\Firefox\Profiles\9f2hzexm.default\shortcutCache\LJCz_WELnnZX07fEwlcIyQ==.ico 6 | IconIndex=0 7 | -------------------------------------------------------------------------------- /py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/py.typed -------------------------------------------------------------------------------- /pylint.rc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # A comma-separated list of package or module names from where C extensions may 4 | # be loaded. Extensions are loading into the active Python interpreter and may 5 | # run arbitrary code 6 | extension-pkg-whitelist= 7 | 8 | # Add files or directories to the blacklist. They should be base names, not 9 | # paths. 10 | ignore=CVS 11 | 12 | # Add files or directories matching the regex patterns to the blacklist. The 13 | # regex matches against base names, not paths. 14 | ignore-patterns= 15 | 16 | # Python code to execute, usually for sys.path manipulation such as 17 | # pygtk.require(). 18 | #init-hook= 19 | 20 | # Use multiple processes to speed up Pylint. 21 | jobs=1 22 | 23 | # List of plugins (as comma separated values of python modules names) to load, 24 | # usually to register additional checkers. 25 | load-plugins= 26 | 27 | # Pickle collected data for later comparisons. 28 | persistent=yes 29 | 30 | # Specify a configuration file. 31 | #rcfile= 32 | 33 | # When enabled, pylint would attempt to guess common misconfiguration and emit 34 | # user-friendly hints instead of false-positive error messages 35 | suggestion-mode=yes 36 | 37 | # Allow loading of arbitrary C extensions. Extensions are imported into the 38 | # active Python interpreter and may run arbitrary code. 39 | unsafe-load-any-extension=no 40 | 41 | 42 | [MESSAGES CONTROL] 43 | 44 | # Only show warnings with the listed confidence levels. Leave empty to show 45 | # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED 46 | confidence= 47 | 48 | # Disable the message, report, category or checker with the given id(s). You 49 | # can either give multiple identifiers separated by comma (,) or put this 50 | # option multiple times (only on the command line, not in the configuration 51 | # file where it should appear only once).You can also use "--disable=all" to 52 | # disable everything first and then reenable specific checks. For example, if 53 | # you want to run only the similarities checker, you can use "--disable=all 54 | # --enable=similarities". If you want to run only the classes checker, but have 55 | # no Warning level messages displayed, use"--disable=all --enable=classes 56 | # --disable=W" 57 | disable=print-statement, 58 | parameter-unpacking, 59 | unpacking-in-except, 60 | old-raise-syntax, 61 | backtick, 62 | long-suffix, 63 | old-ne-operator, 64 | old-octal-literal, 65 | import-star-module-level, 66 | non-ascii-bytes-literal, 67 | invalid-unicode-literal, 68 | raw-checker-failed, 69 | bad-inline-option, 70 | locally-disabled, 71 | locally-enabled, 72 | file-ignored, 73 | suppressed-message, 74 | useless-suppression, 75 | deprecated-pragma, 76 | apply-builtin, 77 | basestring-builtin, 78 | buffer-builtin, 79 | cmp-builtin, 80 | coerce-builtin, 81 | execfile-builtin, 82 | file-builtin, 83 | long-builtin, 84 | raw_input-builtin, 85 | reduce-builtin, 86 | standarderror-builtin, 87 | unicode-builtin, 88 | xrange-builtin, 89 | coerce-method, 90 | delslice-method, 91 | getslice-method, 92 | setslice-method, 93 | no-absolute-import, 94 | old-division, 95 | dict-iter-method, 96 | dict-view-method, 97 | next-method-called, 98 | metaclass-assignment, 99 | indexing-exception, 100 | raising-string, 101 | reload-builtin, 102 | oct-method, 103 | hex-method, 104 | nonzero-method, 105 | cmp-method, 106 | input-builtin, 107 | round-builtin, 108 | intern-builtin, 109 | unichr-builtin, 110 | map-builtin-not-iterating, 111 | zip-builtin-not-iterating, 112 | range-builtin-not-iterating, 113 | filter-builtin-not-iterating, 114 | using-cmp-argument, 115 | eq-without-hash, 116 | div-method, 117 | idiv-method, 118 | rdiv-method, 119 | exception-message-attribute, 120 | invalid-str-codec, 121 | sys-max-int, 122 | bad-python3-import, 123 | deprecated-string-function, 124 | deprecated-str-translate-call, 125 | deprecated-itertools-function, 126 | deprecated-types-field, 127 | next-method-defined, 128 | dict-items-not-iterating, 129 | dict-keys-not-iterating, 130 | dict-values-not-iterating, 131 | deprecated-operator-function, 132 | deprecated-urllib-function, 133 | xreadlines-attribute, 134 | deprecated-sys-function, 135 | exception-escape, 136 | comprehension-escape, 137 | invalid-name, 138 | bad-whitespace, 139 | bad-continuation, 140 | mixed-indentation, 141 | too-many-instance-attributes, 142 | unused-wildcard-import, 143 | no-self-use, 144 | missing-final-newline, 145 | wildcard-import, 146 | redefined-outer-name, 147 | redefine-in-handler, 148 | too-few-public-methods, 149 | too-many-public-methods, 150 | too-many-branches, 151 | too-many-locals, 152 | too-many-ancestors, 153 | too-many-arguments, 154 | too-many-statements, 155 | too-many-nested-blocks, 156 | 157 | # Enable the message, report, category or checker with the given id(s). You can 158 | # either give multiple identifier separated by comma (,) or put this option 159 | # multiple time (only on the command line, not in the configuration file where 160 | # it should appear only once). See also the "--disable" option for examples. 161 | enable=c-extension-no-member 162 | 163 | 164 | [REPORTS] 165 | 166 | # Python expression which should return a note less than 10 (10 is the highest 167 | # note). You have access to the variables errors warning, statement which 168 | # respectively contain the number of errors / warnings messages and the total 169 | # number of statements analyzed. This is used by the global evaluation report 170 | # (RP0004). 171 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 172 | 173 | # Template used to display messages. This is a python new-style format string 174 | # used to format the message information. See doc for all details 175 | #msg-template= 176 | 177 | # Set the output format. Available formats are text, parseable, colorized, json 178 | # and msvs (visual studio).You can also give a reporter class, eg 179 | # mypackage.mymodule.MyReporterClass. 180 | output-format=text 181 | 182 | # Tells whether to display a full report or only the messages 183 | reports=no 184 | 185 | # Activate the evaluation score. 186 | score=yes 187 | 188 | 189 | [REFACTORING] 190 | 191 | # Maximum number of nested blocks for function / method body 192 | max-nested-blocks=5 193 | 194 | # Complete name of functions that never returns. When checking for 195 | # inconsistent-return-statements if a never returning function is called then 196 | # it will be considered as an explicit return statement and no message will be 197 | # printed. 198 | never-returning-functions=optparse.Values,sys.exit 199 | 200 | 201 | [BASIC] 202 | 203 | # Naming style matching correct argument names 204 | argument-naming-style=snake_case 205 | 206 | # Regular expression matching correct argument names. Overrides argument- 207 | # naming-style 208 | #argument-rgx= 209 | 210 | # Naming style matching correct attribute names 211 | attr-naming-style=snake_case 212 | 213 | # Regular expression matching correct attribute names. Overrides attr-naming- 214 | # style 215 | #attr-rgx= 216 | 217 | # Bad variable names which should always be refused, separated by a comma 218 | bad-names=foo, 219 | bar, 220 | baz, 221 | toto, 222 | tutu, 223 | tata 224 | 225 | # Naming style matching correct class attribute names 226 | class-attribute-naming-style=any 227 | 228 | # Regular expression matching correct class attribute names. Overrides class- 229 | # attribute-naming-style 230 | #class-attribute-rgx= 231 | 232 | # Naming style matching correct class names 233 | class-naming-style=PascalCase 234 | 235 | # Regular expression matching correct class names. Overrides class-naming-style 236 | #class-rgx= 237 | 238 | # Naming style matching correct constant names 239 | const-naming-style=UPPER_CASE 240 | 241 | # Regular expression matching correct constant names. Overrides const-naming- 242 | # style 243 | #const-rgx= 244 | 245 | # Minimum line length for functions/classes that require docstrings, shorter 246 | # ones are exempt. 247 | docstring-min-length=-1 248 | 249 | # Naming style matching correct function names 250 | function-naming-style=snake_case 251 | 252 | # Regular expression matching correct function names. Overrides function- 253 | # naming-style 254 | #function-rgx= 255 | 256 | # Good variable names which should always be accepted, separated by a comma 257 | good-names=i, 258 | j, 259 | k, 260 | ex, 261 | Run, 262 | _ 263 | 264 | # Include a hint for the correct naming format with invalid-name 265 | include-naming-hint=no 266 | 267 | # Naming style matching correct inline iteration names 268 | inlinevar-naming-style=any 269 | 270 | # Regular expression matching correct inline iteration names. Overrides 271 | # inlinevar-naming-style 272 | #inlinevar-rgx= 273 | 274 | # Naming style matching correct method names 275 | method-naming-style=snake_case 276 | 277 | # Regular expression matching correct method names. Overrides method-naming- 278 | # style 279 | #method-rgx= 280 | 281 | # Naming style matching correct module names 282 | module-naming-style=snake_case 283 | 284 | # Regular expression matching correct module names. Overrides module-naming- 285 | # style 286 | #module-rgx= 287 | 288 | # Colon-delimited sets of names that determine each other's naming style when 289 | # the name regexes allow several styles. 290 | name-group= 291 | 292 | # Regular expression which should only match function or class names that do 293 | # not require a docstring. 294 | no-docstring-rgx=^_ 295 | 296 | # List of decorators that produce properties, such as abc.abstractproperty. Add 297 | # to this list to register other decorators that produce valid properties. 298 | property-classes=abc.abstractproperty 299 | 300 | # Naming style matching correct variable names 301 | variable-naming-style=snake_case 302 | 303 | # Regular expression matching correct variable names. Overrides variable- 304 | # naming-style 305 | #variable-rgx= 306 | 307 | 308 | [FORMAT] 309 | 310 | # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. 311 | expected-line-ending-format= 312 | 313 | # Regexp for a line that is allowed to be longer than the limit. 314 | ignore-long-lines=^\s*(# )??$ 315 | 316 | # Number of spaces of indent required inside a hanging or continued line. 317 | indent-after-paren=4 318 | 319 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 320 | # tab). 321 | indent-string=' ' 322 | 323 | # Maximum number of characters on a single line. 324 | max-line-length=100 325 | 326 | # Maximum number of lines in a module 327 | max-module-lines=1000 328 | 329 | # List of optional constructs for which whitespace checking is disabled. `dict- 330 | # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. 331 | # `trailing-comma` allows a space between comma and closing bracket: (a, ). 332 | # `empty-line` allows space-only lines. 333 | no-space-check=trailing-comma, 334 | dict-separator 335 | 336 | # Allow the body of a class to be on the same line as the declaration if body 337 | # contains single statement. 338 | single-line-class-stmt=no 339 | 340 | # Allow the body of an if to be on the same line as the test if there is no 341 | # else. 342 | single-line-if-stmt=no 343 | 344 | 345 | [LOGGING] 346 | 347 | # Logging modules to check that the string format arguments are in logging 348 | # function parameter format 349 | logging-modules=logging 350 | 351 | 352 | [MISCELLANEOUS] 353 | 354 | # List of note tags to take in consideration, separated by a comma. 355 | notes=FIXME, 356 | XXX, 357 | TODO 358 | 359 | 360 | [SIMILARITIES] 361 | 362 | # Ignore comments when computing similarities. 363 | ignore-comments=yes 364 | 365 | # Ignore docstrings when computing similarities. 366 | ignore-docstrings=yes 367 | 368 | # Ignore imports when computing similarities. 369 | ignore-imports=no 370 | 371 | # Minimum lines number of a similarity. 372 | min-similarity-lines=4 373 | 374 | 375 | [SPELLING] 376 | 377 | # Limits count of emitted suggestions for spelling mistakes 378 | max-spelling-suggestions=4 379 | 380 | # Spelling dictionary name. Available dictionaries: none. To make it working 381 | # install python-enchant package. 382 | spelling-dict= 383 | 384 | # List of comma separated words that should not be checked. 385 | spelling-ignore-words= 386 | 387 | # A path to a file that contains private dictionary; one word per line. 388 | spelling-private-dict-file= 389 | 390 | # Tells whether to store unknown words to indicated private dictionary in 391 | # --spelling-private-dict-file option instead of raising a message. 392 | spelling-store-unknown-words=no 393 | 394 | 395 | [TYPECHECK] 396 | 397 | # List of decorators that produce context managers, such as 398 | # contextlib.contextmanager. Add to this list to register other decorators that 399 | # produce valid context managers. 400 | contextmanager-decorators=contextlib.contextmanager 401 | 402 | # List of members which are set dynamically and missed by pylint inference 403 | # system, and so shouldn't trigger E1101 when accessed. Python regular 404 | # expressions are accepted. 405 | generated-members= 406 | 407 | # Tells whether missing members accessed in mixin class should be ignored. A 408 | # mixin class is detected if its name ends with "mixin" (case insensitive). 409 | ignore-mixin-members=yes 410 | 411 | # This flag controls whether pylint should warn about no-member and similar 412 | # checks whenever an opaque object is returned when inferring. The inference 413 | # can return multiple potential results while evaluating a Python object, but 414 | # some branches might not be evaluated, which results in partial inference. In 415 | # that case, it might be useful to still emit no-member and other checks for 416 | # the rest of the inferred objects. 417 | ignore-on-opaque-inference=yes 418 | 419 | # List of class names for which member attributes should not be checked (useful 420 | # for classes with dynamically set attributes). This supports the use of 421 | # qualified names. 422 | ignored-classes=optparse.Values,thread._local,_thread._local 423 | 424 | # List of module names for which member attributes should not be checked 425 | # (useful for modules/projects where namespaces are manipulated during runtime 426 | # and thus existing member attributes cannot be deduced by static analysis. It 427 | # supports qualified module names, as well as Unix pattern matching. 428 | ignored-modules= 429 | 430 | # Show a hint with possible names when a member name was not found. The aspect 431 | # of finding the hint is based on edit distance. 432 | missing-member-hint=yes 433 | 434 | # The minimum edit distance a name should have in order to be considered a 435 | # similar match for a missing member name. 436 | missing-member-hint-distance=1 437 | 438 | # The total number of similar names that should be taken in consideration when 439 | # showing a hint for a missing member. 440 | missing-member-max-choices=1 441 | 442 | 443 | [VARIABLES] 444 | 445 | # List of additional names supposed to be defined in builtins. Remember that 446 | # you should avoid to define new builtins when possible. 447 | additional-builtins= 448 | 449 | # Tells whether unused global variables should be treated as a violation. 450 | allow-global-unused-variables=yes 451 | 452 | # List of strings which can identify a callback function by name. A callback 453 | # name must start or end with one of those strings. 454 | callbacks=cb_, 455 | _cb 456 | 457 | # A regular expression matching the name of dummy variables (i.e. expectedly 458 | # not used). 459 | dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ 460 | 461 | # Argument names that match this expression will be ignored. Default to name 462 | # with leading underscore 463 | ignored-argument-names=_.*|^ignored_|^unused_ 464 | 465 | # Tells whether we should check for unused import in __init__ files. 466 | init-import=no 467 | 468 | # List of qualified module names which can have objects that can redefine 469 | # builtins. 470 | redefining-builtins-modules=six.moves,past.builtins,future.builtins,io,builtins 471 | 472 | 473 | [CLASSES] 474 | 475 | # List of method names used to declare (i.e. assign) instance attributes. 476 | defining-attr-methods=__init__, 477 | __new__, 478 | setUp 479 | 480 | # List of member names, which should be excluded from the protected access 481 | # warning. 482 | exclude-protected=_asdict, 483 | _fields, 484 | _replace, 485 | _source, 486 | _make 487 | 488 | # List of valid names for the first argument in a class method. 489 | valid-classmethod-first-arg=cls 490 | 491 | # List of valid names for the first argument in a metaclass class method. 492 | valid-metaclass-classmethod-first-arg=mcs 493 | 494 | 495 | [DESIGN] 496 | 497 | # Maximum number of arguments for function / method 498 | max-args=5 499 | 500 | # Maximum number of attributes for a class (see R0902). 501 | max-attributes=7 502 | 503 | # Maximum number of boolean expressions in a if statement 504 | max-bool-expr=5 505 | 506 | # Maximum number of branch for function / method body 507 | max-branches=12 508 | 509 | # Maximum number of locals for function / method body 510 | max-locals=15 511 | 512 | # Maximum number of parents for a class (see R0901). 513 | max-parents=7 514 | 515 | # Maximum number of public methods for a class (see R0904). 516 | max-public-methods=20 517 | 518 | # Maximum number of return / yield for function / method body 519 | max-returns=6 520 | 521 | # Maximum number of statements in function / method body 522 | max-statements=50 523 | 524 | # Minimum number of public methods for a class (see R0903). 525 | min-public-methods=2 526 | 527 | 528 | [IMPORTS] 529 | 530 | # Allow wildcard imports from modules that define __all__. 531 | allow-wildcard-with-all=no 532 | 533 | # Analyse import fallback blocks. This can be used to support both Python 2 and 534 | # 3 compatible code, which means that the block might have code that exists 535 | # only in one or another interpreter, leading to false positives when analysed. 536 | analyse-fallback-blocks=no 537 | 538 | # Deprecated modules which should not be used, separated by a comma 539 | deprecated-modules=regsub, 540 | TERMIOS, 541 | Bastion, 542 | rexec 543 | 544 | # Create a graph of external dependencies in the given file (report RP0402 must 545 | # not be disabled) 546 | ext-import-graph= 547 | 548 | # Create a graph of every (i.e. internal and external) dependencies in the 549 | # given file (report RP0402 must not be disabled) 550 | import-graph= 551 | 552 | # Create a graph of internal dependencies in the given file (report RP0402 must 553 | # not be disabled) 554 | int-import-graph= 555 | 556 | # Force import order to recognize a module as part of the standard 557 | # compatibility libraries. 558 | known-standard-library= 559 | 560 | # Force import order to recognize a module as part of a third party library. 561 | known-third-party=enchant 562 | 563 | 564 | [EXCEPTIONS] 565 | 566 | # Exceptions that will emit a warning when being caught. Defaults to 567 | # "Exception" 568 | overgeneral-exceptions=Exception 569 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is the setup info for the python installer. 3 | You probably don't need to do anything with it directly. 4 | Just run make and it will be used to create a distributable package 5 | for more info on how this works, see: 6 | http://wheel.readthedocs.org/en/latest/ 7 | and/or 8 | http://pythonwheels.com 9 | """ 10 | import typing 11 | from setuptools import setup,Distribution # type: ignore # pylint: disable=import-error # noqa: E501 12 | 13 | 14 | class BinaryDistribution(Distribution): 15 | """ 16 | Distribution info 17 | """ 18 | def is_pure(self): 19 | """ 20 | Yes, we are pure python 21 | """ 22 | return True # return False if there is OS-specific files 23 | 24 | 25 | def cmdline(args:typing.Iterable[str])->int: 26 | """ 27 | Run the command line 28 | 29 | :param args: command line arguments (WITHOUT the filename) 30 | """ 31 | import os 32 | here=os.path.dirname(os.path.realpath(__file__)) 33 | name='gimpFormats' 34 | # See also: https://setuptools.readthedocs.io/en/latest/setuptools.html 35 | _=args 36 | setup( 37 | name=name, 38 | version='1.0', 39 | description='Pure python implementation of the gimp file format(s)', 40 | long_description='Really, the funniest around.', 41 | classifiers=[ # http://pypi.python.org/pypi?%3Aaction=list_classifiers 42 | 'Development Status :: 3 - Alpha', 43 | #'License :: OSI Approved :: MIT License', 44 | 'Programming Language :: Python :: 3.9', # target python version 45 | # 'Topic :: ', # file this under a topic 46 | ], 47 | #url='http://myproduct.com', 48 | #author='me', 49 | #author_email='x@y.com', 50 | #license='MIT', 51 | packages=[name], 52 | package_dir={name:here}, 53 | package_data={ # add extra files for a package 54 | name:[] 55 | }, 56 | distclass=BinaryDistribution, 57 | install_requires=[], # add dependencies from pypi 58 | dependency_links=[], # add dependency urls (not in pypi) 59 | ) 60 | 61 | 62 | if __name__=='__main__': 63 | import sys 64 | cmdline(sys.argv[1:]) 65 | -------------------------------------------------------------------------------- /test/cli_help/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | test module 3 | """ 4 | from .test import * # type: ignore # noqa: F401,F403,E501,E0401 # pylint: disable=import-error,import-not-found,line-too-long 5 | -------------------------------------------------------------------------------- /test/cli_help/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Run unit tests 5 | 6 | See: 7 | http://pyunit.sourceforge.net/pyunit.html 8 | """ 9 | import unittest 10 | import os 11 | import sys 12 | 13 | 14 | __HERE__=os.path.abspath(__file__).rsplit(os.sep,1)[0]+os.sep 15 | class Test(unittest.TestCase): 16 | """ 17 | Run unit test 18 | 19 | Check the command line help for each module 20 | """ 21 | 22 | def __init__(self,testName): 23 | unittest.TestCase.__init__(self,testName) 24 | self.cmd=None 25 | 26 | def setUp(self): 27 | pass 28 | 29 | def tearDown(self): 30 | pass 31 | 32 | def testName(self): 33 | """ 34 | run the tests 35 | """ 36 | names=[ # scripts that have a command line interface 37 | '../gimpFormat.py', 38 | '../gimpGbrBrush.py', 39 | '../gimpGgrGradient.py', 40 | '../gimpGihBrushSet.py', 41 | '../gimpGpbBrush.py', 42 | '../gimpGplPalette.py', 43 | '../gimpGtpToolPreset.py', 44 | '../gimpPatPattern.py', 45 | '../gimpVbrBrush.py', 46 | '../gimpXcfDocument.py', 47 | ] 48 | for filename in names: 49 | self.runScript(filename,'--help') 50 | 51 | def runScript(self,filename,*args): 52 | """ 53 | run a single script 54 | """ 55 | from importlib.util import spec_from_loader,module_from_spec 56 | from importlib.machinery import SourceFileLoader 57 | path=filename.rsplit(os.sep,1) 58 | module=path[-1].rsplit('.py',1)[0] 59 | path=path[0] 60 | print('Test CLI:',module,filename) 61 | spec = spec_from_loader(module,SourceFileLoader(module,filename)) 62 | module = module_from_spec(spec) 63 | spec.loader.exec_module(module) 64 | if 'cmdline' in dir(module): 65 | print('RUN: '+filename+' '+(' '.join(args))) 66 | module.cmdline(args) 67 | else: 68 | print('SKIP: '+filename+' '+(' '.join(args))) 69 | #print(dir(module)) 70 | 71 | def runScriptOld(self,filename,*args): 72 | """ 73 | This works 100% fine, but does not add to the code coverage metrics 74 | """ 75 | bak=sys.argv 76 | sys.argv=[filename] 77 | sys.argv.extend(args) 78 | print('RUN: '+(' '.join(sys.argv))) 79 | globalz=globals() 80 | #print(globalz) 81 | globalz['__package__']='smartimage' 82 | globalz['__name__']='__main__' 83 | try: 84 | code=open(filename,'r',encoding="utf-8").read() 85 | if code.find('__main__')>=0: 86 | exec(code,globalz) # pylint: disable=exec-used 87 | finally: 88 | sys.argv=bak 89 | 90 | def testSuite(): 91 | """ 92 | Combine unit tests into an entire suite 93 | """ 94 | testSuite = unittest.TestSuite() 95 | testSuite.addTest(Test("testName")) 96 | return testSuite 97 | 98 | 99 | def cmdline(args): 100 | """ 101 | Run the command line 102 | 103 | :param args: command line arguments (WITHOUT the filename) 104 | 105 | Run all the test suites in the standard way. 106 | """ 107 | _=args 108 | unittest.main() 109 | 110 | 111 | if __name__=='__main__': 112 | cmdline(sys.argv[1:]) 113 | -------------------------------------------------------------------------------- /test/exportedPaths/README.txt: -------------------------------------------------------------------------------- 1 | Gimp exports its paths in svg format. The file in this directory is a legit export. -------------------------------------------------------------------------------- /test/exportedPaths/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | test module 3 | """ 4 | from .test import * # type: ignore # noqa: F401,F403,E501,E0401 # pylint: disable=import-error,import-not-found,line-too-long 5 | -------------------------------------------------------------------------------- /test/exportedPaths/lips.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 26 | 27 | -------------------------------------------------------------------------------- /test/gbrBrush/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | test module 3 | """ 4 | from .test import * # type: ignore # noqa: F401,F403,E501,E0401 # pylint: disable=import-error,import-not-found,line-too-long 5 | -------------------------------------------------------------------------------- /test/gbrBrush/desiredOutput_dunes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gbrBrush/desiredOutput_dunes.png -------------------------------------------------------------------------------- /test/gbrBrush/desiredOutput_pepper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gbrBrush/desiredOutput_pepper.png -------------------------------------------------------------------------------- /test/gbrBrush/dunes.gbr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gbrBrush/dunes.gbr -------------------------------------------------------------------------------- /test/gbrBrush/pepper.gbr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gbrBrush/pepper.gbr -------------------------------------------------------------------------------- /test/gbrBrush/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Run unit tests 5 | 6 | See: 7 | http://pyunit.sourceforge.net/pyunit.html 8 | """ 9 | import unittest 10 | import os 11 | from imageTools import compareImage 12 | from gimpFormats import GimpGbrBrush 13 | 14 | 15 | __HERE__=os.path.abspath(__file__).rsplit(os.sep,1)[0]+os.sep 16 | class Test(unittest.TestCase): 17 | """ 18 | Run unit test 19 | """ 20 | 21 | def setUp(self): 22 | self.dut=GimpGbrBrush() 23 | 24 | def tearDown(self): 25 | pass 26 | 27 | def testPepper(self): 28 | """ 29 | Test reading the standard bell pepper brush 30 | """ 31 | self.dut.load(__HERE__+'pepper.gbr') 32 | # test string representation 33 | asStr=repr(self.dut) 34 | assert asStr 35 | # test image saving (implicit) 36 | self.dut.save(__HERE__+'actualOutput_pepper.png') 37 | # test for image match 38 | same=compareImage(self.dut.image,__HERE__+'desiredOutput_pepper.png') 39 | assert same 40 | os.remove(__HERE__+'actualOutput_pepper.png') 41 | # test round-trip compatibility 42 | self.dut.save(__HERE__+'actualOutput_pepper.gbr') 43 | original=open(__HERE__+'pepper.gbr','rb').read() 44 | actual=open(__HERE__+'actualOutput_pepper.gbr','rb').read() 45 | assert actual==original 46 | os.remove(__HERE__+'actualOutput_pepper.gbr') 47 | 48 | def testDunes(self): 49 | """ 50 | Test reading the standard dunes brush 51 | """ 52 | self.dut.load(__HERE__+'dunes.gbr') 53 | # test image saving (explicit) 54 | self.dut.image.save(__HERE__+'actualOutput_dunes.png') 55 | # test string representation 56 | asStr=repr(self.dut)() 57 | assert asStr 58 | # test for image match 59 | same=compareImage(self.dut.image,__HERE__+'desiredOutput_dunes.png') 60 | assert same 61 | os.remove(__HERE__+'actualOutput_dunes.png') 62 | # test round-trip compatibility 63 | self.dut.save(__HERE__+'actualOutput_dunes.gbr') 64 | original=open(__HERE__+'dunes.gbr','rb').read() 65 | actual=open(__HERE__+'actualOutput_dunes.gbr','rb').read() 66 | assert actual==original 67 | os.remove(__HERE__+'actualOutput_dunes.gbr') 68 | 69 | 70 | def testSuite(): 71 | """ 72 | Combine unit tests into an entire suite 73 | """ 74 | testSuite = unittest.TestSuite() 75 | testSuite.addTest(Test("testDunes")) 76 | testSuite.addTest(Test("testPepper")) 77 | return testSuite 78 | 79 | 80 | def cmdline(args): 81 | """ 82 | Run the command line 83 | 84 | :param args: command line arguments (WITHOUT the filename) 85 | 86 | Run all the test suites in the standard way. 87 | """ 88 | _=args 89 | unittest.main() 90 | 91 | 92 | if __name__=='__main__': 93 | import sys 94 | cmdline(sys.argv[1:]) 95 | -------------------------------------------------------------------------------- /test/ggrGradient/Cold_Steel_2.ggr: -------------------------------------------------------------------------------- 1 | GIMP Gradient 2 | Name: Cold Steel 2 3 | 3 4 | 0.000000 0.068447 0.101836 0.143939 0.013928 0.100000 1.000000 1.000000 1.000000 1.000000 1.000000 1 0 5 | 0.101836 0.501669 0.901503 1.000000 1.000000 1.000000 1.000000 0.088684 0.075143 0.174242 1.000000 1 0 6 | 0.901503 0.929902 1.000000 0.200503 0.169888 0.393939 1.000000 0.047059 0.023529 0.137255 1.000000 1 0 7 | -------------------------------------------------------------------------------- /test/ggrGradient/Mexican_flag.ggr: -------------------------------------------------------------------------------- 1 | GIMP Gradient 2 | Name: Mexican flag 3 | 3 4 | 0.000000 0.166667 0.333333 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 0.000000 1.000000 0 0 5 | 0.333333 0.500000 0.666667 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 1.000000 0 0 6 | 0.666667 0.833333 1.000000 1.000000 0.000000 0.000000 1.000000 1.000000 0.000000 0.000000 1.000000 0 0 7 | -------------------------------------------------------------------------------- /test/ggrGradient/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | test module 3 | """ 4 | from .test import * # type: ignore # noqa: F401,F403,E501,E0401 # pylint: disable=import-error,import-not-found,line-too-long 5 | -------------------------------------------------------------------------------- /test/ggrGradient/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Run unit tests 5 | 6 | See: 7 | http://pyunit.sourceforge.net/pyunit.html 8 | """ 9 | import unittest 10 | import os 11 | #from imageTools import compareImage 12 | from gimpFormats import GimpGgrGradient 13 | 14 | 15 | __HERE__=os.path.abspath(__file__).rsplit(os.sep,1)[0]+os.sep 16 | class Test(unittest.TestCase): 17 | """ 18 | Run unit test 19 | """ 20 | 21 | def setUp(self): 22 | self.dut=GimpGgrGradient() 23 | 24 | def tearDown(self): 25 | pass 26 | 27 | def colorArray(self,numPoints): 28 | """ 29 | Turn the colors into an array 30 | """ 31 | colors=[] 32 | i=0.0 33 | inc=1.0/numPoints 34 | while i<1.0: 35 | colors.append(self.dut.getColor(i)) 36 | i+=inc 37 | return colors 38 | 39 | def saveColors(self,f,colorArray): 40 | """ 41 | save the colors to a file 42 | """ 43 | f.write('r, g, b, a\n'.encode('utf-8')) 44 | for c in colorArray: 45 | line=[] 46 | for chan in c: 47 | line.append(str(chan)) 48 | line=', '.join(line)+'\n' 49 | f.write(line.encode('utf-8')) 50 | 51 | def testColdSteel(self): 52 | """ 53 | test loading the standard "cold steel" gradient 54 | """ 55 | self.dut.load(__HERE__+'Cold_Steel_2.ggr') 56 | # test string representation 57 | asStr=repr(self.dut)() 58 | assert asStr 59 | # test round-trip compatibility 60 | self.dut.save(__HERE__+'actualOutput_Cold_Steel_2.ggr') 61 | original=open(__HERE__+'Cold_Steel_2.ggr','rb').read() 62 | actual=open(__HERE__+'actualOutput_Cold_Steel_2.ggr','rb').read() 63 | assert actual==original 64 | os.remove(__HERE__+'actualOutput_Cold_Steel_2.ggr') 65 | # TODO: test calculated colors 66 | #colors=self.colorArray(512) 67 | #actual=open(__HERE__+'actualOutput_Cold_Steel_2.csv','wb') 68 | #saveColors(actual,colors) 69 | 70 | 71 | def testSuite(): 72 | """ 73 | Combine unit tests into an entire suite 74 | """ 75 | testSuite = unittest.TestSuite() 76 | testSuite.addTest(Test("testColdSteel")) 77 | return testSuite 78 | 79 | 80 | def cmdline(args): 81 | """ 82 | Run the command line 83 | 84 | :param args: command line arguments (WITHOUT the filename) 85 | 86 | Run all the test suites in the standard way. 87 | """ 88 | _=args 89 | unittest.main() 90 | 91 | 92 | if __name__=='__main__': 93 | import sys 94 | cmdline(sys.argv[1:]) 95 | -------------------------------------------------------------------------------- /test/gihBrushSet/Wilber.gih: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/Wilber.gih -------------------------------------------------------------------------------- /test/gihBrushSet/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | test module 3 | """ 4 | from .test import * # type: ignore # noqa: F401,F403,E501,E0401 # pylint: disable=import-error,import-not-found,line-too-long 5 | -------------------------------------------------------------------------------- /test/gihBrushSet/actual_Wilber.gih: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/actual_Wilber.gih -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_Wilber_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_Wilber_01.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_Wilber_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_Wilber_02.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_Wilber_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_Wilber_03.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_Wilber_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_Wilber_04.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_01.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_02.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_03.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_04.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_05.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_06.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_07.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_08.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_09.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_10.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_100.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_101.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_101.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_102.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_102.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_103.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_103.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_104.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_104.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_105.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_105.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_106.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_106.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_107.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_107.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_108.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_108.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_109.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_109.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_11.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_110.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_110.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_111.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_111.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_112.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_112.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_113.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_113.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_114.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_115.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_115.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_116.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_116.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_117.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_117.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_118.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_118.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_119.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_119.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_12.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_120.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_121.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_121.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_122.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_122.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_123.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_123.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_124.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_124.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_125.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_125.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_13.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_14.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_15.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_16.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_17.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_18.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_19.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_20.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_21.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_22.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_23.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_24.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_25.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_26.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_27.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_28.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_29.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_30.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_31.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_32.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_33.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_34.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_35.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_36.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_37.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_38.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_39.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_40.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_41.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_42.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_43.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_44.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_45.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_46.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_47.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_48.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_49.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_49.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_50.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_51.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_51.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_52.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_53.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_53.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_54.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_55.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_56.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_57.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_58.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_59.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_59.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_60.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_61.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_61.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_62.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_62.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_63.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_63.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_64.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_65.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_65.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_66.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_66.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_67.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_67.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_68.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_68.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_69.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_69.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_70.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_71.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_71.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_72.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_73.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_73.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_74.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_74.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_75.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_75.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_76.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_77.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_77.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_78.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_78.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_79.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_79.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_80.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_81.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_81.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_82.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_82.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_83.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_83.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_84.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_84.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_85.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_85.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_86.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_86.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_87.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_88.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_88.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_89.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_89.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_90.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_91.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_91.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_92.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_92.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_93.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_93.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_94.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_94.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_95.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_95.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_96.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_97.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_97.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_98.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_98.png -------------------------------------------------------------------------------- /test/gihBrushSet/desiredOutput_feltpen_99.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/desiredOutput_feltpen_99.png -------------------------------------------------------------------------------- /test/gihBrushSet/feltpen.gih: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/gihBrushSet/feltpen.gih -------------------------------------------------------------------------------- /test/gihBrushSet/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Run unit tests 5 | 6 | See: 7 | http://pyunit.sourceforge.net/pyunit.html 8 | """ 9 | import unittest 10 | import os 11 | from imageTools import compareImage 12 | from gimpFormats import GimpGihBrushSet 13 | 14 | 15 | __HERE__=os.path.abspath(__file__).rsplit(os.sep,1)[0]+os.sep 16 | class Test(unittest.TestCase): 17 | """ 18 | Run unit test 19 | """ 20 | 21 | def setUp(self): 22 | self.dut=GimpGihBrushSet() 23 | 24 | def tearDown(self): 25 | pass 26 | 27 | def testBrushSet(self,filename): 28 | """ 29 | :param filename: without extension 30 | """ 31 | source='%s%s.gih'%(__HERE__,filename) 32 | dest='%sactual_%s.gih'%(__HERE__,filename) 33 | self.dut.load(source) 34 | # test string representation 35 | asStr=repr(self.dut)() 36 | assert asStr 37 | # new check all the internal brushes 38 | for id,image in enumerate(self.dut.images): 39 | # test image saving (explicit) 40 | brushActual= '%sactualOutput_%s_%02d.png'%(__HERE__,filename,id+1) 41 | brushDesired='%sdesiredOutput_%s_%02d.png'%(__HERE__,filename,id+1) 42 | image.save(brushActual) 43 | # test for image match 44 | same=compareImage(image,brushDesired) 45 | assert same 46 | os.remove(brushActual) 47 | # test round-trip compatibility 48 | self.dut.save(dest) 49 | original=open(source,'rb').read() 50 | actual=open(dest,'rb').read() 51 | assert actual==original 52 | os.remove(dest) 53 | 54 | def testWilber(self): 55 | """ 56 | test the wilbur brush set 57 | """ 58 | self.testBrushSet('Wilber') 59 | 60 | def testFeltpen(self): 61 | """ 62 | test a felt pen brush set 63 | """ 64 | self.testBrushSet('feltpen') 65 | 66 | 67 | def testSuite(): 68 | """ 69 | Combine unit tests into an entire suite 70 | """ 71 | testSuite = unittest.TestSuite() 72 | testSuite.addTest(Test("testWilber")) 73 | testSuite.addTest(Test("testFeltpen")) 74 | return testSuite 75 | 76 | 77 | def cmdline(args): 78 | """ 79 | Run the command line 80 | 81 | :param args: command line arguments (WITHOUT the filename) 82 | 83 | Run all the test suites in the standard way. 84 | """ 85 | _=args 86 | unittest.main() 87 | 88 | 89 | if __name__=='__main__': 90 | import sys 91 | cmdline(sys.argv[1:]) 92 | -------------------------------------------------------------------------------- /test/gpbBrush/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | test module 3 | """ 4 | from .test import * # type: ignore # noqa: F401,F403,E501,E0401 # pylint: disable=import-error,import-not-found,line-too-long 5 | -------------------------------------------------------------------------------- /test/gplPalette/Plasma.gpl: -------------------------------------------------------------------------------- 1 | GIMP Palette 2 | Name: Plasma 3 | Columns: 16 4 | # 5 | 240 240 0 6 | 240 224 0 7 | 240 208 0 8 | 240 192 0 9 | 240 176 0 10 | 240 160 0 11 | 240 144 0 12 | 240 128 0 13 | 240 112 0 14 | 240 96 0 15 | 240 80 0 16 | 240 64 0 17 | 240 48 0 18 | 240 32 0 19 | 240 16 0 20 | 240 0 0 21 | 224 224 16 22 | 224 212 16 23 | 224 200 16 24 | 224 184 16 25 | 224 172 12 26 | 224 156 12 27 | 224 144 12 28 | 224 128 12 29 | 224 116 8 30 | 224 100 8 31 | 224 88 8 32 | 224 72 8 33 | 224 60 4 34 | 224 44 4 35 | 224 32 4 36 | 224 16 0 37 | 208 208 32 38 | 208 200 32 39 | 208 188 28 40 | 208 176 28 41 | 208 164 24 42 | 208 152 24 43 | 208 140 20 44 | 208 128 20 45 | 208 116 16 46 | 208 104 16 47 | 208 92 12 48 | 208 80 12 49 | 208 68 8 50 | 208 56 8 51 | 208 44 4 52 | 208 32 0 53 | 192 192 48 54 | 192 184 48 55 | 192 176 44 56 | 192 164 40 57 | 192 156 36 58 | 192 144 32 59 | 192 136 32 60 | 192 128 28 61 | 192 116 24 62 | 192 108 20 63 | 192 96 16 64 | 192 88 16 65 | 192 80 12 66 | 192 68 8 67 | 192 60 4 68 | 192 48 0 69 | 176 176 64 70 | 176 172 60 71 | 176 164 56 72 | 176 156 52 73 | 176 148 48 74 | 176 140 44 75 | 176 132 40 76 | 176 124 36 77 | 176 120 32 78 | 176 112 28 79 | 176 104 24 80 | 176 96 20 81 | 176 88 16 82 | 176 80 12 83 | 176 72 8 84 | 176 64 0 85 | 160 160 80 86 | 160 156 76 87 | 160 152 72 88 | 160 144 64 89 | 160 140 60 90 | 160 136 56 91 | 160 128 48 92 | 160 124 44 93 | 160 120 40 94 | 160 112 32 95 | 160 108 28 96 | 160 104 24 97 | 160 96 16 98 | 160 92 12 99 | 160 88 8 100 | 160 80 0 101 | 144 144 96 102 | 144 144 92 103 | 144 140 84 104 | 144 136 80 105 | 144 132 72 106 | 144 128 64 107 | 144 128 60 108 | 144 124 52 109 | 144 120 48 110 | 144 116 40 111 | 144 112 32 112 | 144 112 28 113 | 144 108 20 114 | 144 104 16 115 | 144 100 8 116 | 144 96 0 117 | 128 128 112 118 | 128 128 108 119 | 128 128 100 120 | 128 128 92 121 | 128 124 84 122 | 128 124 76 123 | 128 124 68 124 | 128 124 60 125 | 128 120 56 126 | 128 120 48 127 | 128 120 40 128 | 128 120 32 129 | 128 116 24 130 | 128 116 16 131 | 128 116 8 132 | 128 112 0 133 | 112 112 128 134 | 112 112 120 135 | 112 112 112 grey44 136 | 112 112 104 137 | 112 116 96 138 | 112 116 88 139 | 112 116 80 140 | 112 116 72 141 | 112 120 60 142 | 112 120 52 143 | 112 120 44 144 | 112 120 36 145 | 112 124 28 146 | 112 124 20 147 | 112 124 12 148 | 112 128 0 149 | 96 96 144 150 | 96 96 136 151 | 96 100 128 152 | 96 104 116 153 | 96 108 108 154 | 96 112 96 155 | 96 112 88 156 | 96 116 80 157 | 96 120 68 158 | 96 124 60 159 | 96 128 48 160 | 96 128 40 161 | 96 132 32 162 | 96 136 20 163 | 96 140 12 164 | 96 144 0 165 | 80 80 160 166 | 80 84 152 167 | 80 88 140 168 | 80 96 128 169 | 80 100 120 170 | 80 104 108 171 | 80 112 96 172 | 80 116 88 173 | 80 120 76 174 | 80 128 64 175 | 80 132 56 176 | 80 136 44 177 | 80 144 32 178 | 80 148 24 179 | 80 152 12 180 | 80 160 0 181 | 64 64 176 182 | 64 68 168 183 | 64 76 156 184 | 64 84 144 185 | 64 92 132 186 | 64 100 120 187 | 64 108 108 188 | 64 116 96 189 | 64 120 84 190 | 64 128 72 191 | 64 136 60 192 | 64 144 48 193 | 64 152 36 194 | 64 160 24 195 | 64 168 12 196 | 64 176 0 197 | 48 48 192 198 | 48 56 180 199 | 48 64 168 200 | 48 76 156 201 | 48 84 144 202 | 48 96 128 203 | 48 104 116 204 | 48 112 104 205 | 48 124 92 206 | 48 132 80 207 | 48 144 64 208 | 48 152 52 209 | 48 160 40 210 | 48 172 28 211 | 48 180 16 212 | 48 192 0 213 | 32 32 208 214 | 32 40 196 215 | 32 52 184 216 | 32 64 168 217 | 32 76 156 218 | 32 88 140 219 | 32 100 128 220 | 32 112 112 221 | 32 124 100 222 | 32 136 84 223 | 32 148 72 224 | 32 160 56 225 | 32 172 44 226 | 32 184 28 227 | 32 196 16 228 | 32 208 0 229 | 16 16 224 230 | 16 28 212 231 | 16 40 196 232 | 16 56 180 233 | 16 68 168 234 | 16 84 152 235 | 16 96 136 236 | 16 112 120 237 | 16 124 108 238 | 16 140 92 239 | 16 152 76 240 | 16 168 60 241 | 16 180 48 242 | 16 196 32 243 | 16 208 16 244 | 16 224 0 245 | 0 0 240 246 | 0 16 224 247 | 0 32 208 248 | 0 48 192 249 | 0 64 176 250 | 0 80 160 251 | 0 96 144 252 | 0 112 128 253 | 0 128 112 254 | 0 144 96 255 | 0 160 80 256 | 0 176 64 257 | 0 192 48 258 | 0 208 32 259 | 0 224 16 260 | 0 240 0 261 | -------------------------------------------------------------------------------- /test/gplPalette/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | test module 3 | """ 4 | from .test import * # type: ignore # noqa: F401,F403,E501,E0401 # pylint: disable=import-error,import-not-found,line-too-long 5 | -------------------------------------------------------------------------------- /test/gplPalette/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Run unit tests 5 | 6 | See: 7 | http://pyunit.sourceforge.net/pyunit.html 8 | """ 9 | import unittest 10 | import os 11 | #from imageTools import compareImage 12 | from gimpFormats import GimpGplPalette 13 | 14 | 15 | __HERE__=os.path.abspath(__file__).rsplit(os.sep,1)[0]+os.sep 16 | class Test(unittest.TestCase): 17 | """ 18 | Run unit test 19 | """ 20 | 21 | def setUp(self): 22 | self.dut=GimpGplPalette() 23 | 24 | def tearDown(self): 25 | pass 26 | 27 | def testPlasma(self): 28 | """ 29 | Test with the plasma palette 30 | """ 31 | self.dut.load(__HERE__+'Plasma.gpl') 32 | # test string representation 33 | asStr=repr(self.dut)() 34 | assert asStr 35 | # test round-trip compatibility 36 | self.dut.save(__HERE__+'actualOutput_Plasma.gpl') 37 | original=open(__HERE__+'Plasma.gpl','rb').read() 38 | actual=open(__HERE__+'actualOutput_Plasma.gpl','rb').read() 39 | assert actual==original 40 | os.remove(__HERE__+'actualOutput_Plasma.gpl') 41 | 42 | 43 | def testSuite(): 44 | """ 45 | Combine unit tests into an entire suite 46 | """ 47 | testSuite = unittest.TestSuite() 48 | testSuite.addTest(Test("testPlasma")) 49 | return testSuite 50 | 51 | 52 | def cmdline(args): 53 | """ 54 | Run the command line 55 | 56 | :param args: command line arguments (WITHOUT the filename) 57 | 58 | Run all the test suites in the standard way. 59 | """ 60 | _=args 61 | unittest.main() 62 | 63 | 64 | if __name__=='__main__': 65 | import sys 66 | cmdline(sys.argv[1:]) 67 | -------------------------------------------------------------------------------- /test/gtpToolPreset/4_3-Landscape.gtp: -------------------------------------------------------------------------------- 1 | # GIMP tool preset file 2 | 3 | (icon-name "gimp-tool-crop") 4 | (name "4×3 Landscape") 5 | (tool-options "GimpCropOptions" 6 | (aspect-denominator 3.000000) 7 | (aspect-numerator 4.000000) 8 | (desired-fixed-size-height 300.000000) 9 | (desired-fixed-size-width 400.000000) 10 | (desired-fixed-width 400.000000) 11 | (fixed-rule-active yes) 12 | (overridden-fixed-aspect yes) 13 | (overridden-fixed-size yes) 14 | (tool "gimp-crop-tool")) 15 | (use-fg-bg no) 16 | (use-brush no) 17 | (use-dynamics no) 18 | (use-mypaint-brush no) 19 | (use-gradient no) 20 | (use-pattern no) 21 | (use-palette no) 22 | (use-font no) 23 | 24 | # end of GIMP tool preset file 25 | -------------------------------------------------------------------------------- /test/gtpToolPreset/Smudge-Rough.gtp: -------------------------------------------------------------------------------- 1 | # GIMP tool preset file 2 | 3 | (icon-name "gimp-tool-smudge") 4 | (name "Smudge Rough") 5 | (tool-options "GimpSmudgeOptions" 6 | (tool "gimp-smudge-tool") 7 | (opacity 0.500000) 8 | (brush "Acrylic 01") 9 | (dynamics "Pressure Opacity") 10 | (brush-size 200.000000) 11 | (jitter-amount 0.339806) 12 | (gradient-reverse yes) 13 | (rate 60.000000)) 14 | (use-fg-bg no) 15 | (use-brush yes) 16 | (use-dynamics yes) 17 | (use-gradient no) 18 | (use-pattern no) 19 | (use-palette no) 20 | (use-font no) 21 | 22 | # end of GIMP tool preset file 23 | -------------------------------------------------------------------------------- /test/gtpToolPreset/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | test module 3 | """ 4 | from .test import * # type: ignore # noqa: F401,F403,E501,E0401 # pylint: disable=import-error,import-not-found,line-too-long 5 | -------------------------------------------------------------------------------- /test/gtpToolPreset/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding:utf-8 -*- 3 | """ 4 | Run unit tests 5 | 6 | See: 7 | http://pyunit.sourceforge.net/pyunit.html 8 | """ 9 | import unittest 10 | import os 11 | #from imageTools import compareImage 12 | from gimpFormats import GimpGtpToolPreset 13 | 14 | 15 | __HERE__=os.path.abspath(__file__).rsplit(os.sep,1)[0]+os.sep 16 | class Test(unittest.TestCase): 17 | """ 18 | Run unit test 19 | """ 20 | 21 | def setUp(self): 22 | self.dut=GimpGtpToolPreset() 23 | 24 | def tearDown(self): 25 | pass 26 | 27 | def testSmudgeRough(self): 28 | """ 29 | Test against the smudge rough tool preset 30 | """ 31 | self.dut.load(__HERE__+'Smudge-Rough.gtp') 32 | # test string representation 33 | asStr=repr(self.dut)() 34 | assert asStr 35 | # test round-trip compatibility 36 | self.dut.save(__HERE__+'actualOutput_Smudge-Rough.gtp') 37 | original=open(__HERE__+'Smudge-Rough.gtp','rb').read() 38 | actual=open(__HERE__+'actualOutput_Smudge-Rough.gtp','rb').read() 39 | assert actual==original 40 | os.remove(__HERE__+'actualOutput_Smudge-Rough.gtp') 41 | 42 | 43 | def testSuite(): 44 | """ 45 | Combine unit tests into an entire suite 46 | """ 47 | testSuite = unittest.TestSuite() 48 | testSuite.addTest(Test("testSmudgeRough")) 49 | return testSuite 50 | 51 | 52 | def cmdline(args): 53 | """ 54 | Run the command line 55 | 56 | :param args:command line arguments (WITHOUT the filename) 57 | 58 | Run all the test suites in the standard way. 59 | """ 60 | _=args 61 | unittest.main() 62 | 63 | 64 | if __name__=='__main__': 65 | import sys 66 | cmdline(sys.argv[1:]) 67 | -------------------------------------------------------------------------------- /test/layerGroups/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | test module 3 | """ 4 | from .test import * # type: ignore # noqa: F401,F403,E501,E0401 # pylint: disable=import-error,import-not-found,line-too-long 5 | -------------------------------------------------------------------------------- /test/layerGroups/layer_groups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/layerGroups/layer_groups.png -------------------------------------------------------------------------------- /test/layerGroups/layer_groups.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/layerGroups/layer_groups.xcf -------------------------------------------------------------------------------- /test/patPattern/3dgreen.pat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/patPattern/3dgreen.pat -------------------------------------------------------------------------------- /test/patPattern/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | test module 3 | """ 4 | from .test import * # type: ignore # noqa: F401,F403,E501,E0401 # pylint: disable=import-error,import-not-found,line-too-long 5 | -------------------------------------------------------------------------------- /test/patPattern/desiredOutput_3dgreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/patPattern/desiredOutput_3dgreen.png -------------------------------------------------------------------------------- /test/patPattern/desiredOutput_leopard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/patPattern/desiredOutput_leopard.png -------------------------------------------------------------------------------- /test/patPattern/leopard.pat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/patPattern/leopard.pat -------------------------------------------------------------------------------- /test/patPattern/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding:utf-8 -*- 3 | """ 4 | Run unit tests 5 | 6 | See: 7 | http://pyunit.sourceforge.net/pyunit.html 8 | """ 9 | import unittest 10 | import os 11 | from imageTools import compareImage 12 | from gimpFormats import GimpPatPattern 13 | 14 | 15 | __HERE__=os.path.abspath(__file__).rsplit(os.sep,1)[0]+os.sep 16 | class Test(unittest.TestCase): 17 | """ 18 | Run unit test 19 | """ 20 | 21 | def setUp(self): 22 | self.dut=GimpPatPattern() 23 | 24 | def tearDown(self): 25 | pass 26 | 27 | def test3dgreen(self): 28 | """ 29 | Test loading the 3dgreen patterh 30 | """ 31 | self.dut.load(__HERE__+'3dgreen.pat') 32 | # test string representation 33 | asStr=repr(self.dut)() 34 | assert asStr 35 | # test image saving (implicit) 36 | self.dut.save(__HERE__+'actualOutput_3dgreen.png') 37 | # test for image match 38 | same=compareImage(self.dut.image,__HERE__+'desiredOutput_3dgreen.png') 39 | assert same 40 | os.remove(__HERE__+'actualOutput_3dgreen.png') 41 | # test round-trip compatibility 42 | self.dut.save(__HERE__+'actualOutput_3dgreen.pat') 43 | original=open(__HERE__+'3dgreen.pat','rb').read() 44 | actual=open(__HERE__+'actualOutput_3dgreen.pat','rb').read() 45 | assert actual==original 46 | os.remove(__HERE__+'actualOutput_3dgreen.pat') 47 | 48 | def testLeopard(self): 49 | """ 50 | Test loading the leopard pattern 51 | """ 52 | self.dut.load(__HERE__+'leopard.pat') 53 | # test string representation 54 | asStr=repr(self.dut)() 55 | assert asStr 56 | # test image saving (explicit) 57 | self.dut.image.save(__HERE__+'actualOutput_leopard.png') 58 | # test for image match 59 | same=compareImage(self.dut.image,__HERE__+'desiredOutput_leopard.png') 60 | assert same 61 | os.remove(__HERE__+'actualOutput_leopard.png') 62 | # test round-trip compatibility 63 | self.dut.save(__HERE__+'actualOutput_leopard.pat') 64 | original=open(__HERE__+'leopard.pat','rb').read() 65 | actual=open(__HERE__+'actualOutput_leopard.pat','rb').read() 66 | assert actual==original 67 | os.remove(__HERE__+'actualOutput_leopard.pat') 68 | 69 | 70 | def testSuite(): 71 | """ 72 | Combine unit tests into an entire suite 73 | """ 74 | testSuite = unittest.TestSuite() 75 | testSuite.addTest(Test("test3dgreen")) 76 | testSuite.addTest(Test("testLeopard")) 77 | return testSuite 78 | 79 | 80 | def cmdline(args): 81 | """ 82 | Run the command line 83 | 84 | :param args:command line arguments (WITHOUT the filename) 85 | 86 | Run all the test suites in the standard way. 87 | """ 88 | _=args 89 | unittest.main() 90 | 91 | 92 | if __name__=='__main__': 93 | import sys 94 | cmdline(sys.argv[1:]) 95 | -------------------------------------------------------------------------------- /test/simpleXcfRead/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | test module 3 | """ 4 | from .test import * # type: ignore # noqa: F401,F403,E501,E0401 # pylint: disable=import-error,import-not-found,line-too-long 5 | -------------------------------------------------------------------------------- /test/simpleXcfRead/desiredOutput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/simpleXcfRead/desiredOutput.png -------------------------------------------------------------------------------- /test/simpleXcfRead/one_layer_with_transparency.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/simpleXcfRead/one_layer_with_transparency.xcf -------------------------------------------------------------------------------- /test/simpleXcfRead/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Run unit tests 5 | 6 | See: 7 | http://pyunit.sourceforge.net/pyunit.html 8 | """ 9 | import unittest 10 | import os 11 | from imageTools import compareImage 12 | from gimpFormats import GimpDocument 13 | 14 | 15 | __HERE__=os.path.abspath(__file__).rsplit(os.sep,1)[0]+os.sep 16 | class Test(unittest.TestCase): 17 | """ 18 | Run unit test 19 | """ 20 | 21 | def setUp(self): 22 | self.dut=GimpDocument() 23 | 24 | def tearDown(self): 25 | pass 26 | 27 | def testImage(self): 28 | """ 29 | Test a single-layer image with transparency 30 | """ 31 | self.dut.load(__HERE__+'one_layer_with_transparency.xcf') 32 | # test string representation 33 | asStr=repr(self.dut)() 34 | assert asStr 35 | self.dut.save(__HERE__+'actualOutput.png') 36 | same=compareImage( 37 | self.dut.layers[0].image,__HERE__+'desiredOutput.png') 38 | assert same 39 | 40 | 41 | def testSuite(): 42 | """ 43 | Combine unit tests into an entire suite 44 | """ 45 | testSuite = unittest.TestSuite() 46 | testSuite.addTest(Test("testImage")) 47 | return testSuite 48 | 49 | 50 | def cmdline(args): 51 | """ 52 | Run the command line 53 | 54 | :param args: command line arguments (WITHOUT the filename) 55 | 56 | Run all the test suites in the standard way. 57 | """ 58 | _=args 59 | unittest.main() 60 | 61 | 62 | if __name__=='__main__': 63 | import sys 64 | cmdline(sys.argv[1:]) 65 | -------------------------------------------------------------------------------- /test/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Run unit tests 5 | 6 | See: 7 | http://pyunit.sourceforge.net/pyunit.html 8 | """ 9 | import unittest 10 | import os 11 | import sys 12 | import numpy as np 13 | from imageTools import (makeSameMode,defaultLoader,numpyArray) 14 | 15 | 16 | def compareImage(img1,img2,tolerance=0.99999): 17 | """ 18 | images can be a pil image, rgb numpy array, url, or filename 19 | tolerance - a decimal percent, default is five-nines 20 | """ 21 | img1,img2=makeSameMode([defaultLoader(img1),defaultLoader(img2)]) 22 | img1,img2=numpyArray(img1),numpyArray(img2) 23 | if img1.shape!=img2.shape: 24 | # if they're not even the same size, never mind, then 25 | return False 26 | return np.allclose(img1,img2,rtol=tolerance) 27 | 28 | TESTS=[ 29 | 'cli_help', # make sure those things with command lines are minimally working # noqa: E501 30 | 'exportedPaths', # TODO: Feature not yet implemented 31 | 'gbrBrush', 32 | 'ggrGradient', 33 | 'gihBrushSet', # Broken - size of embedded gbrBrushes are different than standard! # noqa: E501 34 | 'gpbBrush', # TODO: Need to locate sample files from somewhere 35 | 'gplPalette', 36 | 'gtpToolPreset', # Broken - took a big risk rolling my own parser, now I'm paying the price # noqa: E501 # pylint: disable=line-too-long 37 | 'patPattern', 38 | 'vbrBrush', 39 | # --- the rest are .xcf file 40 | #'simpleXcfRead', # has some kind of bug 41 | 'xcfWithSettings', 42 | 'twoLayers', 43 | 'layerGroups', 44 | 'withPaths', 45 | ] 46 | 47 | 48 | __HERE__=os.path.abspath(__file__).rsplit(os.sep,1)[0] 49 | def suite(tests=None): 50 | """ 51 | Combine unit tests into an entire suite 52 | """ 53 | if tests is None or len(tests)==0: 54 | tests=TESTS 55 | testSuite = unittest.TestSuite() 56 | for dirname in TESTS:#os.listdir(__HERE__): 57 | if os.path.isdir(dirname): 58 | fullDirname=__HERE__+os.sep+dirname 59 | if os.path.exists(fullDirname+os.sep+'test.py'): 60 | print('Queueing test: '+dirname) 61 | exec('import '+dirname) # type: ignore # pylint: disable=exec-used # noqa: E501 62 | exec('testSuite.addTest('+dirname+'.testSuite())') # type: ignore # pylint: disable=exec-used # noqa: E501 63 | return testSuite 64 | 65 | 66 | def run(tests=None,output=None,verbosity=2,failfast=False): 67 | """ 68 | Run the tests 69 | """ 70 | if output is None: 71 | output=sys.stdout 72 | else: 73 | output=open(output,'wb') 74 | runner=unittest.TextTestRunner( 75 | output,verbosity=verbosity,failfast=failfast) 76 | runner.run(suite(tests)) 77 | 78 | 79 | def cmdline(args): 80 | """ 81 | Run the command line 82 | 83 | :param args: command line arguments (WITHOUT the filename) 84 | """ 85 | printhelp=False 86 | save=None 87 | tests=[] 88 | if not args: 89 | pass #printhelp=True 90 | else: 91 | for arg in args: 92 | if arg.startswith('-'): 93 | arg=[a.strip() for a in arg.split('=',1)] 94 | if arg[0] in ['-h','--help']: 95 | printhelp=True 96 | elif arg[0]=='--save': 97 | save=arg[1] 98 | else: 99 | print('ERR: unknown argument "'+arg[0]+'"') 100 | else: 101 | tests.append(arg) 102 | if printhelp: 103 | print('Usage:') 104 | print(' test.py [options] [tests]') 105 | print('Options:') 106 | print(' --save=filename ....... save the test output to this file,') 107 | print(' rather than the console') 108 | else: 109 | run(tests,save) 110 | 111 | 112 | if __name__=='__main__': 113 | cmdline(sys.argv[1:]) 114 | -------------------------------------------------------------------------------- /test/twoLayers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | test module 3 | """ 4 | from .test import * # type: ignore # noqa: F401,F403,E501,E0401 # pylint: disable=import-error,import-not-found,line-too-long 5 | -------------------------------------------------------------------------------- /test/twoLayers/two_layers.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/twoLayers/two_layers.xcf -------------------------------------------------------------------------------- /test/vbrBrush/Diagonal-Star-17.vbr: -------------------------------------------------------------------------------- 1 | GIMP-VBR 2 | 1.5 3 | Diagonal Star (17) 4 | circle 5 | 10.000000 6 | 8.500000 7 | 4 8 | 1.000000 9 | 14.000000 10 | 45.000000 11 | -------------------------------------------------------------------------------- /test/vbrBrush/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | test module 3 | """ 4 | from .test import * # type: ignore # noqa: F401,F403,E501,E0401 # pylint: disable=import-error,import-not-found,line-too-long 5 | -------------------------------------------------------------------------------- /test/vbrBrush/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Run unit tests 5 | 6 | See: 7 | http://pyunit.sourceforge.net/pyunit.html 8 | """ 9 | import unittest 10 | import os 11 | from gimpFormats import GimpVbrBrush 12 | 13 | 14 | __HERE__=os.path.abspath(__file__).rsplit(os.sep,1)[0]+os.sep 15 | class Test(unittest.TestCase): 16 | """ 17 | Run unit test 18 | """ 19 | 20 | def setUp(self): 21 | self.dut=GimpVbrBrush() 22 | 23 | def tearDown(self): 24 | pass 25 | 26 | def testDiagonalStar(self): 27 | """ 28 | test the diagonal star brush reads properly 29 | """ 30 | self.dut.load(__HERE__+'Diagonal-Star-17.vbr') 31 | # test string representation 32 | asStr=repr(self.dut) 33 | assert asStr 34 | # test image saving (explicit) 35 | #self.dut.image.save(__HERE__+'actualOutput_Diagonal-Star-17.png') 36 | # test for image match 37 | #same=compareImage(self.dut.image, 38 | # __HERE__+'desiredOutput_Diagonal-Star-17.png') 39 | #assert same 40 | #os.remove(__HERE__+'actualOutput_Diagonal-Star-17.png') 41 | # test round-trip compatibility 42 | self.dut.save(__HERE__+'actualOutput_Diagonal-Star-17.vbr') 43 | original=GimpVbrBrush(__HERE__+'Diagonal-Star-17.vbr') 44 | #open(__HERE__+'Diagonal-Star-17.vbr','rb').read() 45 | actual=self.dut#open( 46 | # __HERE__+'actualOutput_Diagonal-Star-17.vbr','rb').read() 47 | assert actual==original 48 | os.remove(__HERE__+'actualOutput_Diagonal-Star-17.vbr') 49 | 50 | 51 | def testSuite(): 52 | """ 53 | Combine unit tests into an entire suite 54 | """ 55 | testSuite = unittest.TestSuite() 56 | testSuite.addTest(Test("testDiagonalStar")) 57 | return testSuite 58 | 59 | 60 | def cmdline(args): 61 | """ 62 | Run the command line 63 | 64 | :param args: command line arguments (WITHOUT the filename) 65 | 66 | Run all the test suites in the standard way. 67 | """ 68 | _=args 69 | unittest.main() 70 | 71 | 72 | if __name__=='__main__': 73 | import sys 74 | cmdline(sys.argv[1:]) 75 | -------------------------------------------------------------------------------- /test/withPaths/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | test module 3 | """ 4 | from .test import * # type: ignore # noqa: F401,F403,E501,E0401 # pylint: disable=import-error,import-not-found,line-too-long 5 | -------------------------------------------------------------------------------- /test/withPaths/with_paths.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/withPaths/with_paths.xcf -------------------------------------------------------------------------------- /test/xcfWithSettings/__init__.py: -------------------------------------------------------------------------------- 1 | from .test import * -------------------------------------------------------------------------------- /test/xcfWithSettings/test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env 2 | # -*- coding: utf-8 -*- 3 | """ 4 | Run unit tests 5 | 6 | See: 7 | http://pyunit.sourceforge.net/pyunit.html 8 | """ 9 | import unittest 10 | import os 11 | #from imageTools import compareImage 12 | from gimpFormats import GimpDocument 13 | 14 | 15 | __HERE__=os.path.abspath(__file__).rsplit(os.sep,1)[0]+os.sep 16 | class Test(unittest.TestCase): 17 | """ 18 | Run unit test 19 | 20 | This tests a gimp file with selections and other active settings 21 | """ 22 | 23 | def setUp(self): 24 | self.dut=GimpDocument() 25 | 26 | def tearDown(self): 27 | pass 28 | 29 | def testImage(self): 30 | """ 31 | Test image generation 32 | """ 33 | self.dut.load(__HERE__+'with_settings.xcf') 34 | # test string representation 35 | asStr=repr(self.dut) 36 | assert asStr 37 | # test string representation 38 | asStr=repr(self.dut) 39 | assert asStr 40 | #TODO: selection should be the same as the layer's mask 41 | #mask=self.dut.layers[1].mask 42 | for sel in self.dut.vectors: 43 | path=sel.svgPath 44 | print('\nSelection (%d) "%s" = %s'% 45 | (len(sel.strokes),sel.name,path)) 46 | #self.dut.save(__HERE__+'actualOutput.png') 47 | #same=compareImage( 48 | # self.dut.layers[0].image,__HERE__+'desiredOutput.png') 49 | #assert same 50 | 51 | 52 | def testSuite(): 53 | """ 54 | Combine unit tests into an entire suite 55 | """ 56 | testSuite = unittest.TestSuite() 57 | testSuite.addTest(Test("testImage")) 58 | return testSuite 59 | 60 | 61 | def cmdline(args): 62 | """ 63 | Run the command line 64 | 65 | :param args: command line arguments (WITHOUT the filename) 66 | 67 | Run all the test suites in the standard way. 68 | """ 69 | _=args 70 | unittest.main() 71 | 72 | 73 | if __name__=='__main__': 74 | import sys 75 | cmdline(sys.argv[1:]) 76 | -------------------------------------------------------------------------------- /test/xcfWithSettings/with_settings.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheHeadlessSourceMan/gimpFormats/046256826cdee1fe8ffba779a05a96197dc75115/test/xcfWithSettings/with_settings.xcf --------------------------------------------------------------------------------