proper-pixel-art 1.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- proper_pixel_art-1.3.4/.github/CONTRIBUTING.md +43 -0
- proper_pixel_art-1.3.4/.github/workflows/workflow.yml +22 -0
- proper_pixel_art-1.3.4/.gitignore +6 -0
- proper_pixel_art-1.3.4/LICENSE +21 -0
- proper_pixel_art-1.3.4/PKG-INFO +303 -0
- proper_pixel_art-1.3.4/README.md +289 -0
- proper_pixel_art-1.3.4/app.py +8 -0
- proper_pixel_art-1.3.4/assets/anchor/anchor.png +0 -0
- proper_pixel_art-1.3.4/assets/ash/ash.png +0 -0
- proper_pixel_art-1.3.4/assets/ash/mesh.png +0 -0
- proper_pixel_art-1.3.4/assets/ash/result.png +0 -0
- proper_pixel_art-1.3.4/assets/bat/bat.png +0 -0
- proper_pixel_art-1.3.4/assets/bat/mesh.png +0 -0
- proper_pixel_art-1.3.4/assets/bat/result.png +0 -0
- proper_pixel_art-1.3.4/assets/blob/blob.png +0 -0
- proper_pixel_art-1.3.4/assets/blob/closed_edges.png +0 -0
- proper_pixel_art-1.3.4/assets/blob/edges.png +0 -0
- proper_pixel_art-1.3.4/assets/blob/lines.png +0 -0
- proper_pixel_art-1.3.4/assets/blob/mesh.png +0 -0
- proper_pixel_art-1.3.4/assets/blob/result.png +0 -0
- proper_pixel_art-1.3.4/assets/blob/zoom.png +0 -0
- proper_pixel_art-1.3.4/assets/demon/demon.png +0 -0
- proper_pixel_art-1.3.4/assets/demon/mesh.png +0 -0
- proper_pixel_art-1.3.4/assets/demon/result.png +0 -0
- proper_pixel_art-1.3.4/assets/mountain/mesh.png +0 -0
- proper_pixel_art-1.3.4/assets/mountain/mountain.png +0 -0
- proper_pixel_art-1.3.4/assets/mountain/real.jpg +0 -0
- proper_pixel_art-1.3.4/assets/mountain/result.png +0 -0
- proper_pixel_art-1.3.4/assets/pumpkin/mesh.png +0 -0
- proper_pixel_art-1.3.4/assets/pumpkin/pumpkin.png +0 -0
- proper_pixel_art-1.3.4/assets/pumpkin/result.png +0 -0
- proper_pixel_art-1.3.4/proper_pixel_art/__init__.py +0 -0
- proper_pixel_art-1.3.4/proper_pixel_art/cli.py +123 -0
- proper_pixel_art-1.3.4/proper_pixel_art/colors.py +180 -0
- proper_pixel_art-1.3.4/proper_pixel_art/mesh.py +248 -0
- proper_pixel_art-1.3.4/proper_pixel_art/pixelate.py +98 -0
- proper_pixel_art-1.3.4/proper_pixel_art/utils.py +57 -0
- proper_pixel_art-1.3.4/proper_pixel_art/web.py +96 -0
- proper_pixel_art-1.3.4/pyproject.toml +37 -0
- proper_pixel_art-1.3.4/tests/conftest.py +58 -0
- proper_pixel_art-1.3.4/tests/test_mesh.py +16 -0
- proper_pixel_art-1.3.4/tests/test_pixelate.py +43 -0
- proper_pixel_art-1.3.4/uv.lock +1516 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Contributing to Proper Pixel Art
|
|
2
|
+
|
|
3
|
+
Thank you for contributing! Here's how to get started.
|
|
4
|
+
|
|
5
|
+
## Development Setup
|
|
6
|
+
|
|
7
|
+
1. Clone the repository:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git clone https://github.com/KennethJAllen/proper-pixel-art.git
|
|
11
|
+
cd proper-pixel-art
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
2. Install dependencies:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv sync
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Before Submitting a Pull Request
|
|
21
|
+
|
|
22
|
+
Please ensure your code passes all checks:
|
|
23
|
+
|
|
24
|
+
### 1. Format your code
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uv run ruff format
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 2. Check for linting issues
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv run ruff check
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 3. Run tests
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv run pytest -s
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- If changing the main pixelate algorithm, manualy check the results in `tests/outputs/`
|
|
43
|
+
- If necessary, change the number of colors in `tests/conftest.py`
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
on:
|
|
3
|
+
release:
|
|
4
|
+
types: [published]
|
|
5
|
+
jobs:
|
|
6
|
+
publish:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
id-token: write
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
- name: Set up Python
|
|
14
|
+
uses: actions/setup-python@v5
|
|
15
|
+
with:
|
|
16
|
+
python-version: '3.x'
|
|
17
|
+
- name: Install uv
|
|
18
|
+
uses: astral-sh/setup-uv@v4
|
|
19
|
+
- name: Build package
|
|
20
|
+
run: uv build
|
|
21
|
+
- name: Publish to PyPI
|
|
22
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Kenneth Allen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: proper-pixel-art
|
|
3
|
+
Version: 1.3.4
|
|
4
|
+
Summary: Converts pixel-art-style images such as those from generative models or low-quality sprite web uploads to true resolution usable assets.
|
|
5
|
+
Author: Kenneth Allen
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: numpy>=2.2.4
|
|
9
|
+
Requires-Dist: opencv-python>=4.11.0.86
|
|
10
|
+
Requires-Dist: pillow>=11.1.0
|
|
11
|
+
Provides-Extra: web
|
|
12
|
+
Requires-Dist: gradio>=5.0.0; extra == 'web'
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Proper Pixel Art
|
|
16
|
+
|
|
17
|
+
## Summary
|
|
18
|
+
|
|
19
|
+
- Converts noisy, high resolution pixel-art-style images such as those produced by generative models to true pixel resolution assets.
|
|
20
|
+
|
|
21
|
+
- Clean screenshots or low-quality web uploads of sprites.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
### Clone the Repository
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
git clone git@github.com:KennethJAllen/proper-pixel-art.git
|
|
29
|
+
cd proper-pixel-art
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Create Virtual Environment
|
|
33
|
+
|
|
34
|
+
- Install [uv](https://docs.astral.sh/uv/getting-started/installation/) if not already installed.
|
|
35
|
+
- Sync environments
|
|
36
|
+
- `uv sync`
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
First, obtain a source pixel-art-style image (e.g. a pixel-art-style image generated by GPT-4o or a screenshot of pixel-art).
|
|
41
|
+
|
|
42
|
+
### CLI
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv run ppa <input_path> -o <output_path> -c <num_colors> -s <result_scale> [-t]
|
|
46
|
+
# or directly using uvx
|
|
47
|
+
uvx --from https://github.com/KennethJAllen/proper-pixel-art.git ppa <input_path> -o <output_path> -c <num_colors> -s <result_scale> [-t]
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
#### Flags
|
|
51
|
+
|
|
52
|
+
| Flag | Description |
|
|
53
|
+
| -------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
54
|
+
| INPUT (positional) | Source file in pixel-art-style |
|
|
55
|
+
| `-o`, `--output` `<path>` | Output directory or file path for result. (default: '.') |
|
|
56
|
+
| `-c`, `--colors` `<int>` | Number of colors for output. May need to try a few different values. (default 16) |
|
|
57
|
+
| `-s`, `--scale-result` `<int>` | Width/height of each "pixel" in the output. (default: 1) |
|
|
58
|
+
| `-t`, `--transparent` `<bool>` | Output with transparent background. (default: off) |
|
|
59
|
+
| `-u`, `--initial-upscale` `<int>` | Initial image upscale factor. Increasing this may help detect pixel edges. (default 2) |
|
|
60
|
+
| `-w`, `--pixel-width` `<int>` | Width of the pixels in the input image. If not set, it will be determined automatically. (default: None) |
|
|
61
|
+
|
|
62
|
+
#### Example
|
|
63
|
+
|
|
64
|
+
`uv run ppa assets/blob/blob.png -c 16 -s 20 -t`
|
|
65
|
+
|
|
66
|
+
### Python
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from PIL import Image
|
|
70
|
+
from proper_pixel_art.pixelate import pixelate
|
|
71
|
+
|
|
72
|
+
image = Image.open('path/to/input.png')
|
|
73
|
+
result = pixelate(image, num_colors=16)
|
|
74
|
+
result.save('path/to/output.png')
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### Parameters
|
|
78
|
+
|
|
79
|
+
- `image` : `PIL.Image.Image`
|
|
80
|
+
|
|
81
|
+
- A PIL image to pixelate.
|
|
82
|
+
|
|
83
|
+
- `num_colors` : `int`
|
|
84
|
+
|
|
85
|
+
- The number of colors in result.
|
|
86
|
+
- May need to try a few values if the colors don't look right.
|
|
87
|
+
- 8, 16, 32, or 64 typically works.
|
|
88
|
+
|
|
89
|
+
- `initial_upscale` : `int`
|
|
90
|
+
|
|
91
|
+
- Upscale result after algorithm is complete if not None.
|
|
92
|
+
|
|
93
|
+
- `scale_result` : `int`
|
|
94
|
+
|
|
95
|
+
- Upscale initial image. This may help detect lines.
|
|
96
|
+
|
|
97
|
+
- `transparent_background` : `bool`
|
|
98
|
+
- If True, flood fills each corner of the result with transparent alpha.
|
|
99
|
+
|
|
100
|
+
- `intermediate_dir` : `Path | None`
|
|
101
|
+
- Directory to save images visualizing intermediate steps of algorithm. Useful for development.
|
|
102
|
+
|
|
103
|
+
- `pixel_width` : `int | None`
|
|
104
|
+
- Width of the pixels in the input image. If not set, it will be determined automatically. It may be helpful to increase this parameter if not enough pixel edges are being detected.
|
|
105
|
+
|
|
106
|
+
#### Returns
|
|
107
|
+
|
|
108
|
+
A PIL image with true pixel resolution and quantized colors.
|
|
109
|
+
|
|
110
|
+
### Web Interface
|
|
111
|
+
|
|
112
|
+
Local:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
uv sync --extra web
|
|
116
|
+
uv run ppa-web
|
|
117
|
+
# Opens http://127.0.0.1:7860
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Without cloning:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
uvx --from "proper-pixel-art[web]" ppa-web
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Examples
|
|
127
|
+
|
|
128
|
+
The algorithm is robust. It performs well for images that are already approximately alligned to a grid.
|
|
129
|
+
|
|
130
|
+
Here are a few examples. A mesh is computed, where each cell corresponds to one pixel.
|
|
131
|
+
|
|
132
|
+
### Bat
|
|
133
|
+
|
|
134
|
+
- Generated by GPT-4o.
|
|
135
|
+
|
|
136
|
+
<table align="center" width="100%">
|
|
137
|
+
<tr>
|
|
138
|
+
<td width="33%">
|
|
139
|
+
<img src="./assets/bat/bat.png" style="width:100%;" />
|
|
140
|
+
<br><small>Noisy, High Resolution</small>
|
|
141
|
+
</td>
|
|
142
|
+
<td width="33%">
|
|
143
|
+
<img src="./assets/bat/mesh.png" style="width:100%;" />
|
|
144
|
+
<br><small>Mesh</small>
|
|
145
|
+
</td>
|
|
146
|
+
<td width="33%">
|
|
147
|
+
<img src="./assets/bat/result.png" style="width:100%;" />
|
|
148
|
+
<br><small>True Pixel Resolution</small>
|
|
149
|
+
</td>
|
|
150
|
+
</tr>
|
|
151
|
+
</table>
|
|
152
|
+
|
|
153
|
+
### Ash
|
|
154
|
+
|
|
155
|
+
- Screenshot from Google images of Pokemon asset.
|
|
156
|
+
|
|
157
|
+
<table align="center" width="100%">
|
|
158
|
+
<tr>
|
|
159
|
+
<td width="33%">
|
|
160
|
+
<img src="./assets/ash/ash.png" style="width:100%;" />
|
|
161
|
+
<br><small>Noisy, High Resolution</small>
|
|
162
|
+
</td>
|
|
163
|
+
<td width="33%">
|
|
164
|
+
<img src="./assets/ash/mesh.png" style="width:100%;" />
|
|
165
|
+
<br><small>Mesh</small>
|
|
166
|
+
</td>
|
|
167
|
+
<td width="33%">
|
|
168
|
+
<img src="./assets/ash/result.png" style="width:100%;" />
|
|
169
|
+
<br><small>True Pixel Resolution</small>
|
|
170
|
+
</td>
|
|
171
|
+
</tr>
|
|
172
|
+
</table>
|
|
173
|
+
|
|
174
|
+
### Demon
|
|
175
|
+
|
|
176
|
+
- Original image generated by GPT-4o.
|
|
177
|
+
|
|
178
|
+
<table align="center" width="100%">
|
|
179
|
+
<tr>
|
|
180
|
+
<td width="33%">
|
|
181
|
+
<img src="./assets/demon/demon.png" style="width:100%;" />
|
|
182
|
+
<br><small>Noisy, High Resolution</small>
|
|
183
|
+
</td>
|
|
184
|
+
<td width="33%">
|
|
185
|
+
<img src="./assets/demon/mesh.png" style="width:100%;" />
|
|
186
|
+
<br><small>Mesh</small>
|
|
187
|
+
</td>
|
|
188
|
+
<td width="33%">
|
|
189
|
+
<img src="./assets/demon/result.png" style="width:100%;" />
|
|
190
|
+
<br><small>True Pixel Resolution</small>
|
|
191
|
+
</td>
|
|
192
|
+
</tr>
|
|
193
|
+
</table>
|
|
194
|
+
|
|
195
|
+
### Pumpkin
|
|
196
|
+
|
|
197
|
+
- Screenshot from Google Images of Stardew Valley asset. This is an adversarial example as the source image is both low quality and the object is round.
|
|
198
|
+
|
|
199
|
+
<table align="center" width="100%">
|
|
200
|
+
<tr>
|
|
201
|
+
<td width="33%">
|
|
202
|
+
<img src="./assets/pumpkin/pumpkin.png" style="width:100%;" />
|
|
203
|
+
<br><small>Noisy, High Resolution</small>
|
|
204
|
+
</td>
|
|
205
|
+
<td width="33%">
|
|
206
|
+
<img src="./assets/pumpkin/mesh.png" style="width:100%;" />
|
|
207
|
+
<br><small>Mesh</small>
|
|
208
|
+
</td>
|
|
209
|
+
<td width="33%">
|
|
210
|
+
<img src="./assets/pumpkin/result.png" style="width:100%;" />
|
|
211
|
+
<br><small>True Pixel Resolution</small>
|
|
212
|
+
</td>
|
|
213
|
+
</tr>
|
|
214
|
+
</table>
|
|
215
|
+
|
|
216
|
+
## Real Images To Pixel Art
|
|
217
|
+
|
|
218
|
+
- This tool can also be used to convert real images to pixel art by first requesting a pixelated version of the original image from GPT-4o, then using the tool to get the true pixel-resolution image.
|
|
219
|
+
|
|
220
|
+
- Consider this image of a mountain
|
|
221
|
+
|
|
222
|
+
<img src="./assets/mountain/real.jpg" width="50%" alt="Original mountain"/>
|
|
223
|
+
|
|
224
|
+
- Here are the results of first requesting a pixelated version of the mountain, then using the tool to get a true resolution pixel art version.
|
|
225
|
+
|
|
226
|
+
<table align="center" width="100%">
|
|
227
|
+
<tr>
|
|
228
|
+
<td width="33%">
|
|
229
|
+
<img src="./assets/mountain/mountain.png" style="width:100%;" />
|
|
230
|
+
<br><small>Noisy, High Resolution</small>
|
|
231
|
+
</td>
|
|
232
|
+
<td width="33%">
|
|
233
|
+
<img src="./assets/mountain/mesh.png" style="width:100%;" />
|
|
234
|
+
<br><small>Mesh</small>
|
|
235
|
+
</td>
|
|
236
|
+
<td width="33%">
|
|
237
|
+
<img src="./assets/mountain/result.png" style="width:100%;" />
|
|
238
|
+
<br><small>True Pixel Resolution</small>
|
|
239
|
+
</td>
|
|
240
|
+
</tr>
|
|
241
|
+
</table>
|
|
242
|
+
|
|
243
|
+
## Challenges
|
|
244
|
+
|
|
245
|
+
The result of pixel-art style images from LLMs are noisy, high resolution images with a non-uniform grid and random artifacts. Due to these issues, standard downsampling techniques do not work. How can we recover the pixel art with "true" resolution and colors?
|
|
246
|
+
|
|
247
|
+
The current approach to turning pixel art into useable assets for games are either
|
|
248
|
+
|
|
249
|
+
1) Use naive downsampling which does not give a result that is faithful to the original image.
|
|
250
|
+
2) Manually re-create the image in the appropriate resolution pixel by pixel.
|
|
251
|
+
|
|
252
|
+
## Algorithm
|
|
253
|
+
|
|
254
|
+
- The main algorithm solves these challenges. Here is a high level overview. We will apply it step by step on this example image of blob pixel art that was generated from GPT-4o.
|
|
255
|
+
|
|
256
|
+
<img src="./assets/blob/blob.png" width="80%" alt="blob"/>
|
|
257
|
+
|
|
258
|
+
- Note that this image is high resolution and noisy.
|
|
259
|
+
|
|
260
|
+
<img src="./assets/blob/zoom.png" width="80%" alt="The blob is noisy."/>
|
|
261
|
+
|
|
262
|
+
1) Trim the edges of the image and zero out pixels with more than 50% alpha.
|
|
263
|
+
- This is to work around some issues with models such as GPT-4o not giving a perfectly transparent background.
|
|
264
|
+
|
|
265
|
+
2) Upscale by a factor of 2 using nearest neighbor.
|
|
266
|
+
- This can help identify the correct pixel mesh.
|
|
267
|
+
|
|
268
|
+
3) Find edges of the pixel art using [Canny edge detection](https://docs.opencv.org/4.x/da/d22/tutorial_py_canny.html).
|
|
269
|
+
|
|
270
|
+
<img src="./assets/blob/edges.png" width="80%" alt="blob edges"/>
|
|
271
|
+
|
|
272
|
+
4) Close small gaps in edges with a [morphological closing](https://docs.opencv.org/4.x/d9/d61/tutorial_py_morphological_ops.html).
|
|
273
|
+
|
|
274
|
+
<img src="./assets/blob/closed_edges.png" width="80%" alt="blob closed edges"/>
|
|
275
|
+
|
|
276
|
+
5) Take the [probabalistic Hough transform](https://docs.opencv.org/4.x/d3/de6/tutorial_js_houghlines.html) to get the coordinates of lines in the detected edges. Only keep lines that are close to vertical or horizontal giving some grid coordinates. Cluster lines that are closeby together.
|
|
277
|
+
|
|
278
|
+
<img src="./assets/blob/lines.png" width="80%" alt="blob lines"/>
|
|
279
|
+
|
|
280
|
+
6) Find the grid spacing by filtering outliers and taking the median of the spacings, then complete the mesh.
|
|
281
|
+
|
|
282
|
+
<img src="./assets/blob/mesh.png" width="80%" alt="blob mesh"/>
|
|
283
|
+
|
|
284
|
+
7) Quantize the original image to a small number of colors.
|
|
285
|
+
- Note: The result is sensitive to the number of colors chosen.
|
|
286
|
+
- The parameter is not difficult to tune, but the script may need to be re-run if the colors don't look right.
|
|
287
|
+
- 8, 16, 32, or 64 typically works.
|
|
288
|
+
|
|
289
|
+
8) In each cell specified by the mesh, choose the most common color in the cell as the color for the pixel. Recreate the original image with one pixel per cell.
|
|
290
|
+
|
|
291
|
+
- Result upscaled by a factor of $20 \times$ using nearest neighbor.
|
|
292
|
+
|
|
293
|
+
<img src="./assets/blob/result.png" width="80%" alt="blob pixelated"/>
|
|
294
|
+
|
|
295
|
+
## Testing
|
|
296
|
+
|
|
297
|
+
To test algorithm changes and verify output quality:
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
uv run pytest -s
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The tests pixelate all assets in `assets/{name}/{name}.png` and save outputs to `tests/outputs/` for manual visual inspection.
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Proper Pixel Art
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
- Converts noisy, high resolution pixel-art-style images such as those produced by generative models to true pixel resolution assets.
|
|
6
|
+
|
|
7
|
+
- Clean screenshots or low-quality web uploads of sprites.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
### Clone the Repository
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
git clone git@github.com:KennethJAllen/proper-pixel-art.git
|
|
15
|
+
cd proper-pixel-art
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Create Virtual Environment
|
|
19
|
+
|
|
20
|
+
- Install [uv](https://docs.astral.sh/uv/getting-started/installation/) if not already installed.
|
|
21
|
+
- Sync environments
|
|
22
|
+
- `uv sync`
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
First, obtain a source pixel-art-style image (e.g. a pixel-art-style image generated by GPT-4o or a screenshot of pixel-art).
|
|
27
|
+
|
|
28
|
+
### CLI
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
uv run ppa <input_path> -o <output_path> -c <num_colors> -s <result_scale> [-t]
|
|
32
|
+
# or directly using uvx
|
|
33
|
+
uvx --from https://github.com/KennethJAllen/proper-pixel-art.git ppa <input_path> -o <output_path> -c <num_colors> -s <result_scale> [-t]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
#### Flags
|
|
37
|
+
|
|
38
|
+
| Flag | Description |
|
|
39
|
+
| -------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
40
|
+
| INPUT (positional) | Source file in pixel-art-style |
|
|
41
|
+
| `-o`, `--output` `<path>` | Output directory or file path for result. (default: '.') |
|
|
42
|
+
| `-c`, `--colors` `<int>` | Number of colors for output. May need to try a few different values. (default 16) |
|
|
43
|
+
| `-s`, `--scale-result` `<int>` | Width/height of each "pixel" in the output. (default: 1) |
|
|
44
|
+
| `-t`, `--transparent` `<bool>` | Output with transparent background. (default: off) |
|
|
45
|
+
| `-u`, `--initial-upscale` `<int>` | Initial image upscale factor. Increasing this may help detect pixel edges. (default 2) |
|
|
46
|
+
| `-w`, `--pixel-width` `<int>` | Width of the pixels in the input image. If not set, it will be determined automatically. (default: None) |
|
|
47
|
+
|
|
48
|
+
#### Example
|
|
49
|
+
|
|
50
|
+
`uv run ppa assets/blob/blob.png -c 16 -s 20 -t`
|
|
51
|
+
|
|
52
|
+
### Python
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from PIL import Image
|
|
56
|
+
from proper_pixel_art.pixelate import pixelate
|
|
57
|
+
|
|
58
|
+
image = Image.open('path/to/input.png')
|
|
59
|
+
result = pixelate(image, num_colors=16)
|
|
60
|
+
result.save('path/to/output.png')
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### Parameters
|
|
64
|
+
|
|
65
|
+
- `image` : `PIL.Image.Image`
|
|
66
|
+
|
|
67
|
+
- A PIL image to pixelate.
|
|
68
|
+
|
|
69
|
+
- `num_colors` : `int`
|
|
70
|
+
|
|
71
|
+
- The number of colors in result.
|
|
72
|
+
- May need to try a few values if the colors don't look right.
|
|
73
|
+
- 8, 16, 32, or 64 typically works.
|
|
74
|
+
|
|
75
|
+
- `initial_upscale` : `int`
|
|
76
|
+
|
|
77
|
+
- Upscale result after algorithm is complete if not None.
|
|
78
|
+
|
|
79
|
+
- `scale_result` : `int`
|
|
80
|
+
|
|
81
|
+
- Upscale initial image. This may help detect lines.
|
|
82
|
+
|
|
83
|
+
- `transparent_background` : `bool`
|
|
84
|
+
- If True, flood fills each corner of the result with transparent alpha.
|
|
85
|
+
|
|
86
|
+
- `intermediate_dir` : `Path | None`
|
|
87
|
+
- Directory to save images visualizing intermediate steps of algorithm. Useful for development.
|
|
88
|
+
|
|
89
|
+
- `pixel_width` : `int | None`
|
|
90
|
+
- Width of the pixels in the input image. If not set, it will be determined automatically. It may be helpful to increase this parameter if not enough pixel edges are being detected.
|
|
91
|
+
|
|
92
|
+
#### Returns
|
|
93
|
+
|
|
94
|
+
A PIL image with true pixel resolution and quantized colors.
|
|
95
|
+
|
|
96
|
+
### Web Interface
|
|
97
|
+
|
|
98
|
+
Local:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
uv sync --extra web
|
|
102
|
+
uv run ppa-web
|
|
103
|
+
# Opens http://127.0.0.1:7860
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Without cloning:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
uvx --from "proper-pixel-art[web]" ppa-web
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Examples
|
|
113
|
+
|
|
114
|
+
The algorithm is robust. It performs well for images that are already approximately alligned to a grid.
|
|
115
|
+
|
|
116
|
+
Here are a few examples. A mesh is computed, where each cell corresponds to one pixel.
|
|
117
|
+
|
|
118
|
+
### Bat
|
|
119
|
+
|
|
120
|
+
- Generated by GPT-4o.
|
|
121
|
+
|
|
122
|
+
<table align="center" width="100%">
|
|
123
|
+
<tr>
|
|
124
|
+
<td width="33%">
|
|
125
|
+
<img src="./assets/bat/bat.png" style="width:100%;" />
|
|
126
|
+
<br><small>Noisy, High Resolution</small>
|
|
127
|
+
</td>
|
|
128
|
+
<td width="33%">
|
|
129
|
+
<img src="./assets/bat/mesh.png" style="width:100%;" />
|
|
130
|
+
<br><small>Mesh</small>
|
|
131
|
+
</td>
|
|
132
|
+
<td width="33%">
|
|
133
|
+
<img src="./assets/bat/result.png" style="width:100%;" />
|
|
134
|
+
<br><small>True Pixel Resolution</small>
|
|
135
|
+
</td>
|
|
136
|
+
</tr>
|
|
137
|
+
</table>
|
|
138
|
+
|
|
139
|
+
### Ash
|
|
140
|
+
|
|
141
|
+
- Screenshot from Google images of Pokemon asset.
|
|
142
|
+
|
|
143
|
+
<table align="center" width="100%">
|
|
144
|
+
<tr>
|
|
145
|
+
<td width="33%">
|
|
146
|
+
<img src="./assets/ash/ash.png" style="width:100%;" />
|
|
147
|
+
<br><small>Noisy, High Resolution</small>
|
|
148
|
+
</td>
|
|
149
|
+
<td width="33%">
|
|
150
|
+
<img src="./assets/ash/mesh.png" style="width:100%;" />
|
|
151
|
+
<br><small>Mesh</small>
|
|
152
|
+
</td>
|
|
153
|
+
<td width="33%">
|
|
154
|
+
<img src="./assets/ash/result.png" style="width:100%;" />
|
|
155
|
+
<br><small>True Pixel Resolution</small>
|
|
156
|
+
</td>
|
|
157
|
+
</tr>
|
|
158
|
+
</table>
|
|
159
|
+
|
|
160
|
+
### Demon
|
|
161
|
+
|
|
162
|
+
- Original image generated by GPT-4o.
|
|
163
|
+
|
|
164
|
+
<table align="center" width="100%">
|
|
165
|
+
<tr>
|
|
166
|
+
<td width="33%">
|
|
167
|
+
<img src="./assets/demon/demon.png" style="width:100%;" />
|
|
168
|
+
<br><small>Noisy, High Resolution</small>
|
|
169
|
+
</td>
|
|
170
|
+
<td width="33%">
|
|
171
|
+
<img src="./assets/demon/mesh.png" style="width:100%;" />
|
|
172
|
+
<br><small>Mesh</small>
|
|
173
|
+
</td>
|
|
174
|
+
<td width="33%">
|
|
175
|
+
<img src="./assets/demon/result.png" style="width:100%;" />
|
|
176
|
+
<br><small>True Pixel Resolution</small>
|
|
177
|
+
</td>
|
|
178
|
+
</tr>
|
|
179
|
+
</table>
|
|
180
|
+
|
|
181
|
+
### Pumpkin
|
|
182
|
+
|
|
183
|
+
- Screenshot from Google Images of Stardew Valley asset. This is an adversarial example as the source image is both low quality and the object is round.
|
|
184
|
+
|
|
185
|
+
<table align="center" width="100%">
|
|
186
|
+
<tr>
|
|
187
|
+
<td width="33%">
|
|
188
|
+
<img src="./assets/pumpkin/pumpkin.png" style="width:100%;" />
|
|
189
|
+
<br><small>Noisy, High Resolution</small>
|
|
190
|
+
</td>
|
|
191
|
+
<td width="33%">
|
|
192
|
+
<img src="./assets/pumpkin/mesh.png" style="width:100%;" />
|
|
193
|
+
<br><small>Mesh</small>
|
|
194
|
+
</td>
|
|
195
|
+
<td width="33%">
|
|
196
|
+
<img src="./assets/pumpkin/result.png" style="width:100%;" />
|
|
197
|
+
<br><small>True Pixel Resolution</small>
|
|
198
|
+
</td>
|
|
199
|
+
</tr>
|
|
200
|
+
</table>
|
|
201
|
+
|
|
202
|
+
## Real Images To Pixel Art
|
|
203
|
+
|
|
204
|
+
- This tool can also be used to convert real images to pixel art by first requesting a pixelated version of the original image from GPT-4o, then using the tool to get the true pixel-resolution image.
|
|
205
|
+
|
|
206
|
+
- Consider this image of a mountain
|
|
207
|
+
|
|
208
|
+
<img src="./assets/mountain/real.jpg" width="50%" alt="Original mountain"/>
|
|
209
|
+
|
|
210
|
+
- Here are the results of first requesting a pixelated version of the mountain, then using the tool to get a true resolution pixel art version.
|
|
211
|
+
|
|
212
|
+
<table align="center" width="100%">
|
|
213
|
+
<tr>
|
|
214
|
+
<td width="33%">
|
|
215
|
+
<img src="./assets/mountain/mountain.png" style="width:100%;" />
|
|
216
|
+
<br><small>Noisy, High Resolution</small>
|
|
217
|
+
</td>
|
|
218
|
+
<td width="33%">
|
|
219
|
+
<img src="./assets/mountain/mesh.png" style="width:100%;" />
|
|
220
|
+
<br><small>Mesh</small>
|
|
221
|
+
</td>
|
|
222
|
+
<td width="33%">
|
|
223
|
+
<img src="./assets/mountain/result.png" style="width:100%;" />
|
|
224
|
+
<br><small>True Pixel Resolution</small>
|
|
225
|
+
</td>
|
|
226
|
+
</tr>
|
|
227
|
+
</table>
|
|
228
|
+
|
|
229
|
+
## Challenges
|
|
230
|
+
|
|
231
|
+
The result of pixel-art style images from LLMs are noisy, high resolution images with a non-uniform grid and random artifacts. Due to these issues, standard downsampling techniques do not work. How can we recover the pixel art with "true" resolution and colors?
|
|
232
|
+
|
|
233
|
+
The current approach to turning pixel art into useable assets for games are either
|
|
234
|
+
|
|
235
|
+
1) Use naive downsampling which does not give a result that is faithful to the original image.
|
|
236
|
+
2) Manually re-create the image in the appropriate resolution pixel by pixel.
|
|
237
|
+
|
|
238
|
+
## Algorithm
|
|
239
|
+
|
|
240
|
+
- The main algorithm solves these challenges. Here is a high level overview. We will apply it step by step on this example image of blob pixel art that was generated from GPT-4o.
|
|
241
|
+
|
|
242
|
+
<img src="./assets/blob/blob.png" width="80%" alt="blob"/>
|
|
243
|
+
|
|
244
|
+
- Note that this image is high resolution and noisy.
|
|
245
|
+
|
|
246
|
+
<img src="./assets/blob/zoom.png" width="80%" alt="The blob is noisy."/>
|
|
247
|
+
|
|
248
|
+
1) Trim the edges of the image and zero out pixels with more than 50% alpha.
|
|
249
|
+
- This is to work around some issues with models such as GPT-4o not giving a perfectly transparent background.
|
|
250
|
+
|
|
251
|
+
2) Upscale by a factor of 2 using nearest neighbor.
|
|
252
|
+
- This can help identify the correct pixel mesh.
|
|
253
|
+
|
|
254
|
+
3) Find edges of the pixel art using [Canny edge detection](https://docs.opencv.org/4.x/da/d22/tutorial_py_canny.html).
|
|
255
|
+
|
|
256
|
+
<img src="./assets/blob/edges.png" width="80%" alt="blob edges"/>
|
|
257
|
+
|
|
258
|
+
4) Close small gaps in edges with a [morphological closing](https://docs.opencv.org/4.x/d9/d61/tutorial_py_morphological_ops.html).
|
|
259
|
+
|
|
260
|
+
<img src="./assets/blob/closed_edges.png" width="80%" alt="blob closed edges"/>
|
|
261
|
+
|
|
262
|
+
5) Take the [probabalistic Hough transform](https://docs.opencv.org/4.x/d3/de6/tutorial_js_houghlines.html) to get the coordinates of lines in the detected edges. Only keep lines that are close to vertical or horizontal giving some grid coordinates. Cluster lines that are closeby together.
|
|
263
|
+
|
|
264
|
+
<img src="./assets/blob/lines.png" width="80%" alt="blob lines"/>
|
|
265
|
+
|
|
266
|
+
6) Find the grid spacing by filtering outliers and taking the median of the spacings, then complete the mesh.
|
|
267
|
+
|
|
268
|
+
<img src="./assets/blob/mesh.png" width="80%" alt="blob mesh"/>
|
|
269
|
+
|
|
270
|
+
7) Quantize the original image to a small number of colors.
|
|
271
|
+
- Note: The result is sensitive to the number of colors chosen.
|
|
272
|
+
- The parameter is not difficult to tune, but the script may need to be re-run if the colors don't look right.
|
|
273
|
+
- 8, 16, 32, or 64 typically works.
|
|
274
|
+
|
|
275
|
+
8) In each cell specified by the mesh, choose the most common color in the cell as the color for the pixel. Recreate the original image with one pixel per cell.
|
|
276
|
+
|
|
277
|
+
- Result upscaled by a factor of $20 \times$ using nearest neighbor.
|
|
278
|
+
|
|
279
|
+
<img src="./assets/blob/result.png" width="80%" alt="blob pixelated"/>
|
|
280
|
+
|
|
281
|
+
## Testing
|
|
282
|
+
|
|
283
|
+
To test algorithm changes and verify output quality:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
uv run pytest -s
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
The tests pixelate all assets in `assets/{name}/{name}.png` and save outputs to `tests/outputs/` for manual visual inspection.
|