├── .gitignore
├── img
└── thumbnail-example.png
├── thumbnails-assets
├── base
│ ├── blue.png
│ ├── grey.png
│ ├── green.png
│ └── orange.png
├── outline
│ ├── blue.png
│ ├── green.png
│ ├── orange.png
│ ├── pink.png
│ ├── purple.png
│ └── white.png
├── pictogram
│ ├── rock.png
│ ├── wet.png
│ ├── cloud.png
│ ├── finger.png
│ ├── pixel.png
│ ├── sharp.png
│ ├── texture.png
│ └── vegetation.png
└── stroke
│ ├── GDquest_Airbrush_Shading.png
│ └── GDquest_Cloud_Builder_Soft.png
├── src
├── GDquest_Airbrush_Shading.kpp
├── GDquest_Cloud_Builder_Soft.kpp
└── brush data.csv
├── README.md
└── generate_brush_thumbnails.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.psd
2 | *.xlsx
3 | *.afdesign
4 | create_and_name_layers.py
5 | brush_data_to_csv.py
6 |
--------------------------------------------------------------------------------
/img/thumbnail-example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/img/thumbnail-example.png
--------------------------------------------------------------------------------
/thumbnails-assets/base/blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/base/blue.png
--------------------------------------------------------------------------------
/thumbnails-assets/base/grey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/base/grey.png
--------------------------------------------------------------------------------
/src/GDquest_Airbrush_Shading.kpp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/src/GDquest_Airbrush_Shading.kpp
--------------------------------------------------------------------------------
/thumbnails-assets/base/green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/base/green.png
--------------------------------------------------------------------------------
/thumbnails-assets/base/orange.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/base/orange.png
--------------------------------------------------------------------------------
/src/GDquest_Cloud_Builder_Soft.kpp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/src/GDquest_Cloud_Builder_Soft.kpp
--------------------------------------------------------------------------------
/thumbnails-assets/outline/blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/outline/blue.png
--------------------------------------------------------------------------------
/thumbnails-assets/outline/green.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/outline/green.png
--------------------------------------------------------------------------------
/thumbnails-assets/outline/orange.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/outline/orange.png
--------------------------------------------------------------------------------
/thumbnails-assets/outline/pink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/outline/pink.png
--------------------------------------------------------------------------------
/thumbnails-assets/outline/purple.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/outline/purple.png
--------------------------------------------------------------------------------
/thumbnails-assets/outline/white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/outline/white.png
--------------------------------------------------------------------------------
/thumbnails-assets/pictogram/rock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/pictogram/rock.png
--------------------------------------------------------------------------------
/thumbnails-assets/pictogram/wet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/pictogram/wet.png
--------------------------------------------------------------------------------
/thumbnails-assets/pictogram/cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/pictogram/cloud.png
--------------------------------------------------------------------------------
/thumbnails-assets/pictogram/finger.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/pictogram/finger.png
--------------------------------------------------------------------------------
/thumbnails-assets/pictogram/pixel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/pictogram/pixel.png
--------------------------------------------------------------------------------
/thumbnails-assets/pictogram/sharp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/pictogram/sharp.png
--------------------------------------------------------------------------------
/thumbnails-assets/pictogram/texture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/pictogram/texture.png
--------------------------------------------------------------------------------
/thumbnails-assets/pictogram/vegetation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/pictogram/vegetation.png
--------------------------------------------------------------------------------
/src/brush data.csv:
--------------------------------------------------------------------------------
1 | NAME,TYPE,PACK,PICTOGRAM,OUTLINE,BASE
2 | GDquest_Airbrush_Shading,paintbrush,core,,purple,grey
3 | GDquest_Cloud_Builder_Soft,paintbrush,core,cloud,purple,grey
4 |
--------------------------------------------------------------------------------
/thumbnails-assets/stroke/GDquest_Airbrush_Shading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/stroke/GDquest_Airbrush_Shading.png
--------------------------------------------------------------------------------
/thumbnails-assets/stroke/GDquest_Cloud_Builder_Soft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NathanLovato/krita-brush-thumbnails-generator/HEAD/thumbnails-assets/stroke/GDquest_Cloud_Builder_Soft.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Krita brushes thumbnail generator
2 |
3 |
4 |
5 |
6 |
7 | Krita brush presets take time to create. On top of the brush settings, you have to draw your own thumbnails one by one, which adds to the workload. You'll likely reuse backgrounds and pictograms. Editing existing thumbnails can be time-consuming too. As your brush set grows, you'll likely have to redesign some older presets as well.
8 |
9 | That's what this brush thumbnail generator is for. It takes some sprites, Krita brush presets, CSV data and generates new brush thumbnails.
10 |
11 | I use it for the [Krita brushes for game artist](https://gumroad.com/l/krita-brushes-for-game-artists) and the [free Krita Brushes](https://github.com/GDquest/free-krita-brushes/)
12 |
13 | ## How to use
14 |
15 | ### The sprites
16 |
17 | All sprites should be the same size! Krita uses 200px * 200px pictures for its presets' thumbnails.
18 |
19 | Each thumbnail has up to four layers:
20 |
21 | 1. The base, or its background
22 | 2. The stroke
23 | 3. The outline
24 | 4. The pictogram
25 |
26 |
27 | The tool comes with demo assets. Feel free to use them to design your own brushes, and run the script to see how it works (see below).
28 |
29 | ### CSV data
30 |
31 | The CSV file needs to be encoded as utf-8, and the data comma delimited.
32 |
33 | Keep the header in the file, and fill each row with:
34 |
35 | 1. NAME: the preset's filename without the extension
36 | 2. TYPE (_optional_): the brush engine you use ; not used by the script
37 | 3. PACK (_optional_): to sort all your presets if you produce several bundles
38 | 4. PICTOGRAM: the pictogram's filename without the extension
39 | 5. OUTLINE: the outline's filename without the extension
40 | 6. BASE: the base's filename without the extension
41 |
42 | ### Prerequisites
43 |
44 | Python 3 with the Pillow image library installed.
45 |
46 | If you use pip to install python packages, in the command line, type:
47 |
48 | ```shell
49 | $ pip install Pillow
50 | ```
51 |
52 | See the full install guide on [Pillow's documentation](http://pillow.readthedocs.io/en/4.0.x/installation.html)
53 |
54 | ### The source presets
55 |
56 | Place the CSV file and the source brush presets in the /src folder.
57 |
58 | To pick a different source folder, edit the generate_brush_thumbnails.py file. Change `USE_SRC_FOLDER` to `False`. A file picker will pop up and let you choose another directory.
59 |
60 | ### Run the script
61 |
62 | Open a shell in the brush generator's folder, and run
63 |
64 | ```shell
65 | $ python generate_brush_thumbnails.py
66 | ```
67 |
68 | Within a few seconds, it'll generate the brush presets. The script will overwrite files in the /dist folder, if any.
69 |
70 |
71 | ## License
72 |
73 | All the code and png files in this repository are available under the MIT license. Other files are only present for demo purposes, and not available to distribute.
74 |
75 | Copyright 2017 Nathan Lovato (GDquest)
76 |
77 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
78 |
79 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
80 |
81 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
82 |
--------------------------------------------------------------------------------
/generate_brush_thumbnails.py:
--------------------------------------------------------------------------------
1 | """
2 | Takes a list of .kpp paintoppreset files, generates thumbnails
3 | with PIL, and saves them as .kpp krita paintoppresets
4 | """
5 | import os
6 | import tkinter as tk
7 | from tkinter import filedialog
8 | import codecs
9 | import csv
10 | from PIL import Image, PngImagePlugin
11 |
12 | from distutils.dir_util import copy_tree
13 | from subprocess import Popen
14 |
15 | # -------
16 | # OPTIONS
17 | # -------
18 | ASSETS_FOLDER = os.path.realpath("thumbnails-assets")
19 | SAVE_FOLDER = os.path.realpath("dist")
20 | USE_SRC_FOLDER = True
21 | OPTIMIZE = True
22 |
23 |
24 | # ---------
25 | # FUNCTIONS
26 | # ---------
27 | def get_brush_parameters(file_path):
28 | """Generator that yields brush file data from a CSV file"""
29 | with codecs.open(csv_file_path, 'r', 'utf-8-sig') as data:
30 | csv_iterable = csv.reader(data)
31 | header = next(csv_iterable)
32 | for row in csv_iterable:
33 | brush = dict(zip(header, row))
34 | yield brush
35 |
36 |
37 | def create_thumbnail(base, *args):
38 | """
39 | returns a thumbnail Image
40 | Uses Image.alpha_composite to generate the thumbnail image
41 | using any amount of layers. *args must be PIL images
42 | """
43 | composite = base
44 | for layer in args:
45 | if layer is None:
46 | continue
47 | if layer.size != composite.size:
48 | layer = layer.crop(composite.getbbox())
49 | if layer.mode != composite.mode:
50 | layer = layer.convert(mode=composite.mode)
51 | composite = Image.alpha_composite(composite, layer)
52 | return composite
53 |
54 |
55 | def get_brush_metadata(image):
56 | """
57 | Takes a PIL Image object and returns a PngInfo object
58 | that contains the file's metadata to override pnginfo
59 | on save.
60 | """
61 | metadata = PngImagePlugin.PngInfo()
62 | for key, value in image.info.items():
63 | if key not in RESERVED:
64 | metadata.add_text(key, value, 0)
65 | return metadata
66 |
67 | # ------
68 | # SCRIPT
69 | # ------
70 | # Get the paths of files that contains the thumbnails and csv data
71 | tk.Tk().withdraw()
72 | start_folder = './src' if USE_SRC_FOLDER else filedialog.askdirectory(
73 | ).replace('/', '\\')
74 | file_names = [f for f in os.listdir(start_folder)]
75 |
76 | # Get the CSV
77 | csv_file_path = next(os.path.join(start_folder, name)
78 | for name in file_names if name.endswith('.csv'))
79 | brush_names = [name[:-4] for name in file_names if name.endswith('.kpp')]
80 | brush_paths = {name: os.path.join(start_folder, name + ".kpp")
81 | for name in brush_names}
82 |
83 | if not csv_file_path:
84 | raise AttributeError('Missing CSV file')
85 | if not brush_names:
86 | raise AttributeError('No paintoppreset found (.kpp files)')
87 |
88 | # Store the presets' data from the CSV
89 | brush_data = []
90 | for brush in get_brush_parameters(file_path=csv_file_path):
91 | for name in brush_names:
92 | if name == brush['NAME']:
93 | brush['FILE'] = name
94 | brush_data.append(brush)
95 |
96 | # Preload all sprites to compose the final thumbnail
97 | assets = {}
98 | for directory in os.listdir(ASSETS_FOLDER):
99 | directory_path = ASSETS_FOLDER + "\\" + directory
100 | assets[directory] = {}
101 | for asset in os.listdir(directory_path):
102 | if asset.endswith('.png'):
103 | assets[directory][asset[:-4]] = Image.open(directory_path + "\\" +
104 | asset)
105 |
106 | if not os.path.exists(SAVE_FOLDER):
107 | os.mkdir(SAVE_FOLDER)
108 |
109 | # Process each file based on CSV data
110 | brushes_count = len(brush_data)
111 | progress = 0
112 | RESERVED = ('interlace', 'gamma', 'dpi', 'transparency', 'aspect')
113 | for brush in brush_data:
114 | brush_metadata = None
115 | brush_name = brush['FILE']
116 | if brush_name in brush_paths.keys():
117 | with Image.open(brush_paths[brush_name]) as source:
118 | brush_metadata = get_brush_metadata(image=source)
119 | if not brush_metadata:
120 | print('Metadata not found for {!s}, couldn\'t create thumbnail'.format(brush_name))
121 | continue
122 |
123 | xml_data = brush_metadata.chunks
124 | base = assets['base'][brush['BASE']]
125 | outline = assets['outline'][brush['OUTLINE']]
126 | try:
127 | stroke = assets['stroke'][brush['NAME']]
128 | except:
129 | stroke = None
130 | try:
131 | pictogram = assets['pictogram'][brush['PICTOGRAM']]
132 | except:
133 | pictogram = None
134 | thumbnail = create_thumbnail(base, stroke, outline, pictogram)
135 |
136 | filename = brush['NAME'] + ".kpp"
137 | thumbnail.save(
138 | os.path.join(SAVE_FOLDER, filename),
139 | format='png',
140 | pnginfo=brush_metadata,
141 | optimize=OPTIMIZE)
142 |
143 | progress += 1
144 | print("\rProgress: {!s}/{!s}".format(progress, brushes_count),
145 | end="",
146 | flush=True)
147 |
148 |
149 | # TODO: Copy thumbs to Krita presets folder for testing
150 | # krita_preset_folder = r'C:\Users\natlo_000\AppData\Roaming\krita\paintoppresets'
151 | # copy_tree(output_presets, krita_preset_folder)
152 | # Popen(['explorer', krita_preset_folder])
153 |
--------------------------------------------------------------------------------