├── .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 |
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
--------------------------------------------------------------------------------