pixel-widget 0.1.0__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.
- pixel_widget-0.1.0/PKG-INFO +1206 -0
- pixel_widget-0.1.0/README.md +1190 -0
- pixel_widget-0.1.0/pyproject.toml +30 -0
- pixel_widget-0.1.0/src/pixel_widget/__init__.py +19 -0
- pixel_widget-0.1.0/src/pixel_widget/lib.py +1220 -0
|
@@ -0,0 +1,1206 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pixel-widget
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A neat desktop overlay pixel grid widget using Pygame and NumPy
|
|
5
|
+
Project-URL: Homepage, https://github.com/DFB000
|
|
6
|
+
Author-email: Fribus Danila <dbfribus4@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
12
|
+
Requires-Python: >=3.8
|
|
13
|
+
Requires-Dist: numpy>=1.20.0
|
|
14
|
+
Requires-Dist: pygame>=2.0.0
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# PixelWidget
|
|
18
|
+
|
|
19
|
+
Маленькое пиксельное окно для Windows на `pygame` и `numpy`. Вы рисуете на сетке (по умолчанию 32×32 пикселя), а библиотека показывает её в окне без рамки, которое всегда лежит поверх остальных окон и прижато к углу экрана. Подходит для виджетов, индикаторов, мини-дисплеев, игр на сетке, экспериментов с пиксельной графикой.
|
|
20
|
+
|
|
21
|
+
Окно бывает двух видов, и функции у них **одни и те же**:
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
w = PixelWidget() # чёрно-белое окно (по умолчанию)
|
|
25
|
+
w = PixelWidget(rgb=True) # то же окно, но с цветом
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Работает и из обычного кода, и из `asyncio`: синхронному коду `asyncio` не нужен.
|
|
29
|
+
|
|
30
|
+
**Содержание**
|
|
31
|
+
|
|
32
|
+
1. [Установка и запуск](#1-установка-и-запуск)
|
|
33
|
+
2. [Основные понятия за 3 минуты](#2-основные-понятия-за-3-минуты)
|
|
34
|
+
3. [Сравнение с OpenCV (cv2)](#3-сравнение-с-opencv-cv2)
|
|
35
|
+
4. [Окно: создание, положение, размер](#4-окно-создание-положение-размер)
|
|
36
|
+
5. [Цвет](#5-цвет)
|
|
37
|
+
6. [Рисование: пиксели и матрицы](#6-рисование-пиксели-и-матрицы)
|
|
38
|
+
7. [Рисование: фигуры, текст, картинки, градиент](#7-рисование-фигуры-текст-картинки-градиент)
|
|
39
|
+
8. [Кадры и анимация](#8-кадры-и-анимация)
|
|
40
|
+
9. [Слои](#9-слои)
|
|
41
|
+
10. [Image: картинка в памяти](#10-image-картинка-в-памяти)
|
|
42
|
+
11. [Синхронизация, отладка, закрытие](#11-синхронизация-отладка-закрытие)
|
|
43
|
+
12. [Ошибки](#12-ошибки)
|
|
44
|
+
13. [Демо-примеры](#13-демо-примеры)
|
|
45
|
+
14. [Частые проблемы и ограничения](#14-частые-проблемы-и-ограничения)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 1. Установка и запуск
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
pip install pygame numpy
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Положите `lib.py` рядом со своим скриптом:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from lib import PixelWidget
|
|
59
|
+
|
|
60
|
+
w = PixelWidget()
|
|
61
|
+
w.text(4, 13, "HELLO")
|
|
62
|
+
w.wait()
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 2. Основные понятия за 3 минуты
|
|
69
|
+
|
|
70
|
+
### Сетка и координаты
|
|
71
|
+
|
|
72
|
+
Картинка состоит из пикселей. В чёрно-белом режиме пиксель либо включён (белый), либо выключен (чёрный). В RGB режиме у пикселя есть цвет, а «выключен» значит чёрный. Точка `(0, 0)` в левом верхнем углу, `x` растёт вправо, `y` растёт вниз. Всё, что выходит за границы сетки, молча обрезается.
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
(0,0) ──── x ────►
|
|
76
|
+
│
|
|
77
|
+
y
|
|
78
|
+
│
|
|
79
|
+
▼
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Рисуем прямо на окне
|
|
83
|
+
|
|
84
|
+
У `PixelWidget` есть все методы рисования, и они сразу меняют то, что видно в окне:
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
w = PixelWidget()
|
|
88
|
+
w.rect(2, 2, 10, 6)
|
|
89
|
+
w.text(1, 20, "HI")
|
|
90
|
+
w.set_pixel(0, 0)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Один набор функций для двух режимов
|
|
94
|
+
|
|
95
|
+
Всё, что работает в чёрно-белом режиме, работает и в RGB. В RGB добавляется необязательный аргумент `color=`:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
w = PixelWidget(rgb=True)
|
|
99
|
+
w.rect(2, 2, 10, 6, color="red")
|
|
100
|
+
w.text(1, 20, "HI", color="#00ff88")
|
|
101
|
+
w.set_pixel(0, 0) # без color рисуется белым
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
В чёрно-белом режиме `color=` недоступен и даёт понятную ошибку, а не молча игнорируется.
|
|
105
|
+
|
|
106
|
+
### Три действия: включить, погасить, инвертировать
|
|
107
|
+
|
|
108
|
+
Это основа всей библиотеки. Для пикселей, матриц и фигур одна и та же идея:
|
|
109
|
+
|
|
110
|
+
| Действие | Пиксель | Матрица (маска) | Без аргументов | У фигур |
|
|
111
|
+
|---|---|---|---|---|
|
|
112
|
+
| **включить** (покрасить) | `set_pixel(x, y)` | `set_pixel(matrix)` | залить всё | `mode="set"` |
|
|
113
|
+
| **погасить** | `clear_pixel(x, y)` | `clear_pixel(matrix)` | очистить всё | `mode="clear"` |
|
|
114
|
+
| **инвертировать** | `xor_pixel(x, y)` | `xor_pixel(matrix)` | инвертировать всё | `mode="xor"` |
|
|
115
|
+
|
|
116
|
+
Матрица — это массив `numpy` **того же размера, что и сетка**. Действие применяется к тем пикселям, где в матрице стоит не ноль, остальные не меняются (подробнее в [разделе 6](#6-рисование-пиксели-и-матрицы)).
|
|
117
|
+
|
|
118
|
+
### Кадр: нарисовать и показать целиком
|
|
119
|
+
|
|
120
|
+
Если рисуете сложную картинку из нескольких фигур, окно может показать её недорисованной (мерцание). Решение: нарисовать всё в черновике и показать целиком.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
with w.frame() as f:
|
|
124
|
+
f.rect(2, 2, 8, 8)
|
|
125
|
+
f.text(0, 20, "OK")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Внутри блока вы рисуете на `f` (это пустая картинка, [Image](#10-image-картинка-в-памяти), с теми же методами, что у окна). Когда блок закончился, картинка целиком заменяет содержимое окна. Каждый кадр начинается с чистого листа, поэтому очищать экран не нужно.
|
|
129
|
+
|
|
130
|
+
### Слои
|
|
131
|
+
|
|
132
|
+
Слои как прозрачные плёнки: что нарисовано на нижних, видно сквозь верхние. Типичный случай: фон и надпись поверх него. Стираете надпись, а фон остаётся. Всё, что вы рисуете прямо на `w`, попадает в слой `"main"`.
|
|
133
|
+
|
|
134
|
+
### Image: картинка в памяти
|
|
135
|
+
|
|
136
|
+
`Image` — это такая же сетка пикселей с теми же методами рисования, но не привязанная к окну: что на ней нарисовано, видно, только если вы вставите её в окно (`w.paste(image, x, y)`). Нужна для заготовок (значок, спрайт) и внутри `with w.frame() as f` (там `f` — это `Image`).
|
|
137
|
+
|
|
138
|
+
### Как работает окно (для любопытных)
|
|
139
|
+
|
|
140
|
+
Ваш код меняет картинку в памяти мгновенно. Отдельный фоновый поток сам замечает изменение и перерисовывает окно, поэтому вызовы не блокируют вас. Нужно убедиться, что картинка уже на экране? Вызовите `w.flush()`.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 3. Сравнение с OpenCV (cv2)
|
|
145
|
+
|
|
146
|
+
Идея та же: вы вызываете функции рисования фигур. Отличия в деталях:
|
|
147
|
+
|
|
148
|
+
| cv2 | PixelWidget | Что иначе |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| `cv2.rectangle(img, (x1, y1), (x2, y2), color, thickness)` | `w.rect(x, y, w, h, fill, mode, color=)` | Вместо второго угла ширина и высота. Вместо `thickness=-1` есть `fill=True`. Толщина контура 1 пиксель |
|
|
151
|
+
| `cv2.line(img, (x1, y1), (x2, y2), color)` | `w.line(x0, y0, x1, y1, mode, color=)` | Координаты отдельными числами, не кортежами |
|
|
152
|
+
| `cv2.circle(img, (cx, cy), r, color, thickness)` | `w.circle(cx, cy, r, fill, mode, color=)` | `fill=True` вместо `thickness=-1` |
|
|
153
|
+
| `cv2.putText(img, text, (x, y), font, scale, color)` | `w.text(x, y, text, mode, spacing, color=)` | `(x, y)` — **левый верхний** угол текста (в cv2 левый нижний). Один шрифт 3×5, без масштаба |
|
|
154
|
+
| `img[y0:y1, x0:x1] = roi` | `w.paste(picture, x, y)` | Умеет прозрачность и `xor`, сам обрезает по краям |
|
|
155
|
+
| `img[y, x] = (b, g, r)` | `w[x, y] = (r, g, b)` или `w.set_pixel(x, y, color=...)` | **Порядок: x первым, цвет в RGB** (в cv2 строка первой, цвет в BGR) |
|
|
156
|
+
| `img[y, x]` | `w[x, y]` или `w.get_pixel(x, y)` | |
|
|
157
|
+
| `cv2.bitwise_or(img, mask)` | `w.set_pixel(mask)` | Маска должна совпадать по размеру с сеткой |
|
|
158
|
+
| `cv2.bitwise_and` с инверсией маски | `w.clear_pixel(mask)` | |
|
|
159
|
+
| `cv2.bitwise_xor(img, mask)` | `w.xor_pixel(mask)` | |
|
|
160
|
+
| `cv2.bitwise_not(img)` | `w.xor_pixel()` | |
|
|
161
|
+
| `img[:] = 0` | `w.clear_pixel()` | |
|
|
162
|
+
| `img[:] = matrix` | `w.replace(matrix)` | |
|
|
163
|
+
| `np.roll(img, shift)` | `w.scroll(dx, dy, wrap=True)` | |
|
|
164
|
+
| `cv2.addWeighted` с затемнением | `w.brightness = 0.5` | |
|
|
165
|
+
| `cv2.imshow` | `with w.frame() as f: ...` | Окно создаётся один раз, кадры обновляют его |
|
|
166
|
+
| `cv2.imwrite` | `w.save_png(path)` | |
|
|
167
|
+
| `cv2.waitKey` / `time.sleep` | `w.flush()`, `w.wait()` | `flush()` ждёт, пока кадр реально показан |
|
|
168
|
+
| `cv2.cvtColor(..., HSV2BGR)` | `hsv(h, s, v)` | |
|
|
169
|
+
|
|
170
|
+
Чего в cv2 нет: слои, кадры-транзакции, режим `xor` у каждой фигуры, встроенный асинхронный режим.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## 4. Окно: создание, положение, размер
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
w = PixelWidget(width=32, height=32, window_size=320, title="Pinned Widget",
|
|
178
|
+
position=(3840, 1032), fallback_position=(1920, 1008), anchor="bottom_right",
|
|
179
|
+
fps=30, always_on_top=True, hide_from_taskbar=True, strict=False,
|
|
180
|
+
rgb=False, on_color=None, off_color=None, brightness=1.0)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
| Параметр | По умолчанию | Что означает |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| `width`, `height` | `32`, `32` | Размер сетки в пикселях (сколько точек вы рисуете) |
|
|
186
|
+
| `window_size` | `320` | Размер окна на экране в пикселях. Число — квадратное окно, пара `(ширина, высота)` — прямоугольное |
|
|
187
|
+
| `title` | `"Pinned Widget"` | Заголовок окна |
|
|
188
|
+
| `position` | `(3840, 1032)` | Точка экрана, к которой **прижимается угол окна** (какой именно угол — задаёт `anchor`) |
|
|
189
|
+
| `fallback_position` | `(1920, 1008)` | Та же точка, если по `position` нет монитора (второй монитор отключён) |
|
|
190
|
+
| `anchor` | `"bottom_right"` | Какой угол окна стоит в точке `position`: `"top_left"`, `"top_right"`, `"bottom_left"`, `"bottom_right"` |
|
|
191
|
+
| `fps` | `30` | Как часто окно проверяет изменения. 30 достаточно |
|
|
192
|
+
| `always_on_top` | `True` | Окно всегда поверх остальных. Раз в полсекунды позиция подтверждается заново, свёрнутое окно возвращается |
|
|
193
|
+
| `hide_from_taskbar` | `True` | Скрыть окно с панели задач и из Alt+Tab |
|
|
194
|
+
| `strict` | `False` | `True`: `set_pixel(x, y)` за границей сетки бросает `IndexError` вместо тихого игнорирования |
|
|
195
|
+
| `rgb` | `False` | `True`: цветное окно, у функций рисования появляется `color=` |
|
|
196
|
+
| `on_color`, `off_color` | белый, чёрный | Только для чёрно-белого режима: каким цветом показывать включённые и выключенные пиксели (например, «зелёный экран»). С `rgb=True` указывать нельзя |
|
|
197
|
+
| `brightness` | `1.0` | Яркость окна от 0 до 1, работает в обоих режимах |
|
|
198
|
+
|
|
199
|
+
### Как окно прижимается к углу (и почему оно не «уезжает» при смене размера)
|
|
200
|
+
|
|
201
|
+
Положение задаётся **углом окна**, а не его левым верхним краем. По умолчанию это правый нижний угол: он стоит в точке `position`, а остальное окно вычисляется от него. Поэтому, как бы вы ни меняли `window_size`, окно остаётся в том же углу экрана:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
PixelWidget(window_size=320) # правый нижний угол окна в (3840, 1032)
|
|
205
|
+
PixelWidget(window_size=160) # то же место, окно просто меньше
|
|
206
|
+
PixelWidget(window_size=(256, 64)) # прямоугольное окно, угол там же
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Нужно прижать к другому углу? Укажите точку этого угла и `anchor`:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
PixelWidget(position=(100, 100), anchor="top_left") # левый верхний угол окна в (100, 100)
|
|
213
|
+
PixelWidget(position=(1920, 0), anchor="top_right") # правый верхний угол в (1920, 0)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Если по `position` нет монитора, окно ставится в `fallback_position` тем же способом (тем же углом).
|
|
217
|
+
|
|
218
|
+
### Свойства
|
|
219
|
+
|
|
220
|
+
| Свойство | Что это |
|
|
221
|
+
|---|---|
|
|
222
|
+
| `w.width`, `w.height` | Размер сетки |
|
|
223
|
+
| `w.window_width`, `w.window_height` | Размер окна на экране |
|
|
224
|
+
| `w.position` | Левый верхний угол окна на экране (уже с учётом `anchor` и запасной позиции) |
|
|
225
|
+
| `w.rgb` | `True`, если окно цветное |
|
|
226
|
+
| `w.running` | `True`, пока окно открыто |
|
|
227
|
+
| `w.layers` | Имена слоёв снизу вверх, например `["main", "hud"]` |
|
|
228
|
+
| `w.color` | Текущий цвет рисования (только RGB, см. [раздел 5](#5-цвет)) |
|
|
229
|
+
| `w.on_color`, `w.off_color` | Цвета включённых и выключенных пикселей (только чёрно-белый режим; в RGB режиме читаются как `None`) |
|
|
230
|
+
| `w.brightness` | Яркость окна, можно менять на лету |
|
|
231
|
+
|
|
232
|
+
`PixelWidget` можно использовать как менеджер контекста: окно закроется само.
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
with PixelWidget() as w:
|
|
236
|
+
w.set_pixel(5, 5)
|
|
237
|
+
w.flush()
|
|
238
|
+
time.sleep(2)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## 5. Цвет
|
|
244
|
+
|
|
245
|
+
### Включение
|
|
246
|
+
|
|
247
|
+
Одним аргументом: `PixelWidget(rgb=True)`. Дальше все функции рисования получают необязательный аргумент `color=` (только по имени, чтобы его не спутать с координатами).
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
w = PixelWidget(rgb=True)
|
|
251
|
+
w.set_pixel(3, 4, color="red")
|
|
252
|
+
w.rect(0, 0, 8, 8, color=(255, 128, 0))
|
|
253
|
+
w.line(0, 0, 31, 31, color="#00ff88")
|
|
254
|
+
w.circle(16, 16, 5, fill=True, color=0xFF00FF)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Если `color=` не указан, берётся **текущий цвет** (по умолчанию белый), поэтому код из чёрно-белого режима работает в RGB без правок.
|
|
258
|
+
|
|
259
|
+
### Как можно задавать цвет
|
|
260
|
+
|
|
261
|
+
| Способ | Примеры |
|
|
262
|
+
|---|---|
|
|
263
|
+
| Имя | `"red"`, `"green"`, `"blue"`, `"yellow"`, `"cyan"`, `"magenta"`, `"orange"`, `"purple"`, `"pink"`, `"gray"`, `"brown"`, `"teal"`, `"navy"`, `"lime"`, `"white"`, `"black"` |
|
|
264
|
+
| Hex-строка | `"#ff8800"`, `"ff8800"`, `"#f80"` |
|
|
265
|
+
| Кортеж или список из 3 чисел 0..255 | `(255, 136, 0)`, `[255, 136, 0]` |
|
|
266
|
+
| Целое число `0xRRGGBB` | `0xFF8800` |
|
|
267
|
+
|
|
268
|
+
Имена не зависят от регистра. Все цвета в формате RGB (красный, зелёный, синий), как в вебе, а не BGR, как в cv2.
|
|
269
|
+
|
|
270
|
+
### Текущий цвет: `w.color`
|
|
271
|
+
|
|
272
|
+
Чтобы не повторять `color=` в каждом вызове:
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
w.color = "red"
|
|
276
|
+
w.rect(0, 0, 8, 8) # красный
|
|
277
|
+
w.text(1, 10, "HI") # тоже красный
|
|
278
|
+
w.rect(10, 0, 8, 8, color="blue") # color= перекрывает текущий цвет на один вызов
|
|
279
|
+
w.color = "white"
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Текущий цвет есть у окна, у каждого слоя и у каждого `Image`. Кадр из `with w.frame() as f` начинает с цвета того слоя, на который выводится.
|
|
283
|
+
|
|
284
|
+
### Хелперы для цвета
|
|
285
|
+
|
|
286
|
+
| Функция | Что делает |
|
|
287
|
+
|---|---|
|
|
288
|
+
| `hsv(h, s=1.0, v=1.0)` | Цвет из оттенка `h` (градусы 0..360, дальше по кругу), насыщенности и яркости (0..1). Возвращает кортеж `(r, g, b)`. Удобно для радуги |
|
|
289
|
+
| `mix(color1, color2, t)` | Смесь двух цветов: `t=0` даёт первый, `t=1` второй. Принимает любой формат цвета |
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
from lib import hsv, mix
|
|
293
|
+
|
|
294
|
+
for x in range(32):
|
|
295
|
+
w.rect(x, 0, 1, 8, color=hsv(x * 360 / 32)) # радуга
|
|
296
|
+
w.set_pixel(5, 5, color=mix("red", "blue", 0.5)) # фиолетовый
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Как три действия работают в цвете
|
|
300
|
+
|
|
301
|
+
| Действие | Что происходит с цветным пикселем |
|
|
302
|
+
|---|---|
|
|
303
|
+
| `set` | Пиксель получает цвет |
|
|
304
|
+
| `clear` | Пиксель становится чёрным (цвет игнорируется) |
|
|
305
|
+
| `xor` | Цвет пикселя побитово смешивается с цветом (XOR по каждому каналу). Дважды тем же цветом возвращает исходный. С белым — инверсия цвета |
|
|
306
|
+
|
|
307
|
+
### Чёрный — это «пусто»
|
|
308
|
+
|
|
309
|
+
В RGB режиме чёрный пиксель считается пустым. Поэтому:
|
|
310
|
+
|
|
311
|
+
- **Слои:** где на верхнем слое чёрный пиксель, виден нижний слой. Нельзя положить непрозрачный чёрный пиксель поверх цветного.
|
|
312
|
+
- **Картинки:** чёрные пиксели вставляемой цветной картинки прозрачные (в режиме `transparent=True`).
|
|
313
|
+
- **Матрицы:** в RGB-матрице чёрный `(0, 0, 0)` значит «этого пикселя в картинке нет».
|
|
314
|
+
|
|
315
|
+
Если вам нужен именно чёрный фон, рисуйте его на нижнем слое окна (там чёрный и так по умолчанию).
|
|
316
|
+
|
|
317
|
+
### Яркость и цвета ч/б окна
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
w.brightness = 0.5 # затемнить окно вдвое, в любом режиме
|
|
321
|
+
|
|
322
|
+
w = PixelWidget(on_color="lime", off_color="#001000") # ч/б окно в стиле «зелёный экран»
|
|
323
|
+
w.on_color = "orange" # цвета можно менять на лету
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
`on_color` и `off_color` влияют только на показ: рисование остаётся чёрно-белым, и `color=` по-прежнему недоступен. Хотите по-настоящему разные цвета у фигур — включайте `rgb=True`.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## 6. Рисование: пиксели и матрицы
|
|
331
|
+
|
|
332
|
+
Методы этого раздела и следующего есть **у всех, на чём можно рисовать**: у окна `w`, у слоя, у кадра `f` из `with w.frame() as f` и у `Image`. Везде работают одинаково.
|
|
333
|
+
|
|
334
|
+
### `set_pixel`, `clear_pixel`, `xor_pixel`
|
|
335
|
+
|
|
336
|
+
Каждый из трёх методов вызывается тремя способами:
|
|
337
|
+
|
|
338
|
+
| Вызов | Что происходит |
|
|
339
|
+
|---|---|
|
|
340
|
+
| `set_pixel(x, y)` | Действие над одним пикселем |
|
|
341
|
+
| `set_pixel(matrix)` | Действие над всеми пикселями, где в матрице не ноль |
|
|
342
|
+
| `set_pixel()` | Действие над всей картинкой |
|
|
343
|
+
|
|
344
|
+
Действие: `set_pixel` включает (красит), `clear_pixel` гасит, `xor_pixel` переключает.
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
w.set_pixel(3, 4) # включить пиксель
|
|
348
|
+
w.xor_pixel(3, 4) # теперь он погас
|
|
349
|
+
w.set_pixel(mask) # включить всё по маске
|
|
350
|
+
w.clear_pixel(mask) # погасить всё по маске
|
|
351
|
+
w.xor_pixel(mask) # переключить всё по маске
|
|
352
|
+
w.set_pixel() # включить все пиксели
|
|
353
|
+
w.xor_pixel() # инвертировать всю картинку
|
|
354
|
+
w.clear_pixel() # очистить всё
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
В RGB режиме у `set_pixel` и `xor_pixel` есть `color=`, у `clear_pixel` нет (гасить можно только в чёрный):
|
|
358
|
+
|
|
359
|
+
```python
|
|
360
|
+
w.set_pixel(3, 4, color="red")
|
|
361
|
+
w.set_pixel(mask, color="blue") # маска красится одним цветом
|
|
362
|
+
w.set_pixel(color="green") # залить всё зелёным
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Пиксель за границей сетки игнорируется (при `strict=True`: `IndexError`).
|
|
366
|
+
|
|
367
|
+
### Матрицы
|
|
368
|
+
|
|
369
|
+
Матрица — массив `numpy` **ровно того же размера, что и сетка**. Подойдёт и список списков, и `Image` того же размера. Бывает двух видов:
|
|
370
|
+
|
|
371
|
+
| Вид | Форма | Что значит | Где работает |
|
|
372
|
+
|---|---|---|---|
|
|
373
|
+
| **Маска** | `(height, width)` | не ноль = «этот пиксель» | в обоих режимах |
|
|
374
|
+
| **Цветная картинка** | `(height, width, 3)` | цвета пикселей, целые 0..255 | только `rgb=True` |
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
import numpy as np
|
|
378
|
+
|
|
379
|
+
mask = np.zeros((32, 32), dtype=bool)
|
|
380
|
+
mask[8:24, 8:24] = True
|
|
381
|
+
w.set_pixel(mask) # включили квадрат
|
|
382
|
+
w.xor_pixel(np.eye(32)) # переключили диагональ
|
|
383
|
+
w.clear_pixel(mask) # погасили квадрат
|
|
384
|
+
|
|
385
|
+
picture = np.zeros((32, 32, 3), dtype=np.uint8)
|
|
386
|
+
picture[:, :16] = (255, 0, 0) # левая половина красная
|
|
387
|
+
picture[:, 16:] = (0, 0, 255) # правая синяя
|
|
388
|
+
w_rgb.set_pixel(picture) # в RGB окне
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Правила:
|
|
392
|
+
|
|
393
|
+
- **Маска.** Любое число, не равное нулю, значит «включено» (`True`, `1`, `255`, `0.5`...). Ноль и `False` — «выключено». В RGB окне маска красится цветом `color=` (или текущим).
|
|
394
|
+
- **Цветная матрица.** Пиксель «есть», если он не чёрный. `set_pixel(picture)` копирует цвета таких пикселей, `clear_pixel(picture)` гасит их, `xor_pixel(picture)` смешивает цвета.
|
|
395
|
+
- Действие применяется только к пикселям, которые «есть» в матрице. Остальные **не меняются**. Поэтому два вызова `set_pixel(a)` и `set_pixel(b)` дают объединение, а не замену.
|
|
396
|
+
- Матрица не того размера — всегда ошибка (`ValueError`), а не тихая обрезка. Так вы сразу заметите перепутанные оси.
|
|
397
|
+
- Подойдут числа и логический тип в масках, целые 0..255 в цветных матрицах. Строки, `None`, `NaN`, дробные числа в цветных матрицах дают ошибку (см. [Ошибки](#12-ошибки)).
|
|
398
|
+
|
|
399
|
+
### `replace(matrix, color=None)`
|
|
400
|
+
|
|
401
|
+
Заменяет картинку целиком: пиксели, которые в матрице есть, горят, остальные гаснут. Размер матрицы тоже должен совпадать с сеткой. Удобно, когда вы вычисляете кадр целиком (в RGB режиме можно сразу передать `(H, W, 3)`).
|
|
402
|
+
|
|
403
|
+
```python
|
|
404
|
+
frame = np.random.rand(32, 32) > 0.5
|
|
405
|
+
w.replace(frame)
|
|
406
|
+
|
|
407
|
+
w_rgb.replace(np.random.randint(0, 256, (32, 32, 3)))
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
### `get_pixel(x, y)` и чтение
|
|
411
|
+
|
|
412
|
+
Что сейчас на экране в точке `(x, y)`:
|
|
413
|
+
|
|
414
|
+
- в чёрно-белом режиме `0` или `1`;
|
|
415
|
+
- в RGB режиме кортеж `(r, g, b)`.
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
w.get_pixel(3, 4) # 1
|
|
419
|
+
w_rgb.get_pixel(3, 4) # (255, 0, 0)
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Координата за границей сетки здесь всегда `IndexError` (читать несуществующее нельзя).
|
|
423
|
+
|
|
424
|
+
### Индексация: `w[x, y]`
|
|
425
|
+
|
|
426
|
+
Короткая запись для чтения и записи пикселей. **Первым идёт `x`**, как во всей библиотеке (не как в `numpy`, где первая строка).
|
|
427
|
+
|
|
428
|
+
```python
|
|
429
|
+
w[3, 4] = "red" # RGB: покрасить пиксель
|
|
430
|
+
w[3, 4] = True # включить (белый)
|
|
431
|
+
w[3, 4] = False # погасить
|
|
432
|
+
w[0:8, 0:8] = "blue" # закрасить квадрат 8×8 (срезы: [x_от:x_до, y_от:y_до])
|
|
433
|
+
w[:, 20] = "green" # закрасить целую строку y = 20
|
|
434
|
+
print(w[3, 4]) # прочитать пиксель
|
|
435
|
+
region = w[0:8, 0:8] # прочитать область: массив формы (h, w) или (h, w, 3)
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
- В чёрно-белом режиме присваивать можно `True`/`False` или `1`/`0`.
|
|
439
|
+
- В RGB режиме любой формат цвета; `True` — белый, `False` — чёрный (целое число — это `0xRRGGBB`, поэтому `0` тоже чёрный).
|
|
440
|
+
- Индексы — обычные координаты (без отрицательных «с конца»): за границей пиксель игнорируется, как у `set_pixel`. Срезы со `step` не поддерживаются.
|
|
441
|
+
|
|
442
|
+
---
|
|
443
|
+
|
|
444
|
+
## 7. Рисование: фигуры, текст, картинки, градиент
|
|
445
|
+
|
|
446
|
+
Все методы этого раздела принимают `mode`:
|
|
447
|
+
|
|
448
|
+
| mode | Что делает | Когда нужен |
|
|
449
|
+
|---|---|---|
|
|
450
|
+
| `"set"` (по умолчанию) | Включает пиксели (красит цветом) | Обычное рисование |
|
|
451
|
+
| `"clear"` | Гасит пиксели | Стереть фигуру, вырезать дырку |
|
|
452
|
+
| `"xor"` | Переключает: горевший гаснет, погасший загорается | Курсор, мигание, «негатив» |
|
|
453
|
+
|
|
454
|
+
В RGB режиме они же принимают `color=`. Всё, что выходит за границы, обрезается. Любая фигура стирается тем же вызовом с `mode="clear"`.
|
|
455
|
+
|
|
456
|
+
### `rect(x, y, w, h, fill=True, mode="set", color=None)`
|
|
457
|
+
|
|
458
|
+
Прямоугольник. `(x, y)` — левый верхний угол, `w` и `h` — ширина и высота в пикселях. `fill=False` рисует только контур шириной в 1 пиксель. Ширина или высота равна 0 — ничего не рисуется, отрицательные — `ValueError`.
|
|
459
|
+
|
|
460
|
+
```python
|
|
461
|
+
w.rect(0, 0, 32, 32, fill=False) # рамка по краю экрана
|
|
462
|
+
w.rect(10, 10, 8, 8) # закрашенный квадрат
|
|
463
|
+
w.rect(10, 10, 8, 8, mode="clear") # стереть его
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### `line(x0, y0, x1, y1, mode="set", color=None)`
|
|
467
|
+
|
|
468
|
+
Прямая линия от точки `(x0, y0)` до `(x1, y1)`. Работает в любом направлении, концы могут быть за экраном.
|
|
469
|
+
|
|
470
|
+
### `circle(cx, cy, r, fill=False, mode="set", color=None)`
|
|
471
|
+
|
|
472
|
+
Окружность с центром `(cx, cy)` и радиусом `r` (не меньше 0). `fill=True` закрашивает круг.
|
|
473
|
+
|
|
474
|
+
### `text(x, y, text, mode="set", spacing=1, color=None)`
|
|
475
|
+
|
|
476
|
+
Выводит текст шрифтом 3×5 пикселя. `(x, y)` — левый верхний угол первого символа. `spacing` — промежуток между буквами. `\n` переносит на следующую строку. Возвращает `x`, где закончился текст.
|
|
477
|
+
|
|
478
|
+
Поддерживаются цифры, латинские `A`–`Z` (строчные рисуются как заглавные), пробел и знаки `: . , - + ! ? / % = _ ( ) * '`. Всё остальное выводится как `?`.
|
|
479
|
+
|
|
480
|
+
```python
|
|
481
|
+
end_x = w.text(0, 20, "OK") # end_x = 8
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### `paste(picture, x=0, y=0, mode="set", transparent=True, color=None)`
|
|
485
|
+
|
|
486
|
+
Вставляет картинку `picture` в точку `(x, y)` (её левый верхний угол). Картинка может быть любого размера, даже меньше сетки. Допустимые виды:
|
|
487
|
+
|
|
488
|
+
- `Image` (в том числе слой), чёрно-белый или цветной,
|
|
489
|
+
- массив `numpy` или список списков: 2D (маска) или 3D `(h, w, 3)` (цветная, только `rgb=True`),
|
|
490
|
+
- ASCII-строка (`"#"` — включено, всё остальное — выключено).
|
|
491
|
+
|
|
492
|
+
Как красятся пиксели:
|
|
493
|
+
|
|
494
|
+
- **Маска** (ч/б `Image`, 2D-массив, ASCII-строка): включённые пиксели красятся цветом `color=` (в RGB окне) или текущим.
|
|
495
|
+
- **Цветная картинка** (цветной `Image`, 3D-массив): пиксели сохраняют **свои цвета**, `color=` игнорируется.
|
|
496
|
+
- Цветную картинку нельзя вставить в чёрно-белое окно (`ValueError`).
|
|
497
|
+
|
|
498
|
+
Координаты могут быть отрицательными: часть, вылезающая за экран, обрезается.
|
|
499
|
+
|
|
500
|
+
- `transparent=True` (по умолчанию): затрагиваются только пиксели картинки, которые «есть» (не чёрные). Остальное остаётся как было. С `mode="clear"` картинка работает как трафарет, вырезающий дырку.
|
|
501
|
+
- `transparent=False`: область под картинкой заменяется целиком, нули тоже. Работает только с `mode="set"`.
|
|
502
|
+
|
|
503
|
+
```python
|
|
504
|
+
w.paste(".#.\n###", 5, 5) # прямо ASCII-строкой
|
|
505
|
+
w.paste(ship, 13, 24) # готовым Image
|
|
506
|
+
w_rgb.paste(".#.\n###", 5, 5, color="cyan") # маска красится в цвет
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### `gradient(x, y, w, h, color1, color2, direction="horizontal", mode="set")`
|
|
510
|
+
|
|
511
|
+
Только `rgb=True`. Заливает прямоугольник плавным переходом от `color1` к `color2`.
|
|
512
|
+
|
|
513
|
+
- `direction="horizontal"`: цвет меняется слева направо; `"vertical"`: сверху вниз.
|
|
514
|
+
- `mode="set"` (по умолчанию) закрашивает область градиентом, `"xor"` смешивает градиент с тем, что там уже есть, `"clear"` стирает область.
|
|
515
|
+
|
|
516
|
+
```python
|
|
517
|
+
w_rgb.gradient(0, 0, 32, 8, "red", "blue")
|
|
518
|
+
w_rgb.gradient(0, 10, 8, 20, "black", "white", direction="vertical")
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### `scroll(dx=0, dy=0, wrap=False)`
|
|
522
|
+
|
|
523
|
+
Сдвигает всю картинку на `dx` вправо и `dy` вниз (отрицательные значения — влево и вверх). `wrap=False`: вылезшее за край пропадает, освободившееся место пустое. `wrap=True`: вылезшее появляется с противоположной стороны.
|
|
524
|
+
|
|
525
|
+
### Про тип координат
|
|
526
|
+
|
|
527
|
+
Все координаты, размеры и радиусы должны быть целыми числами. Подойдут `int`, целые из `numpy` и «целые» дробные (`5.0`). Дробное с остатком (`1.5`) или строка дают `TypeError`.
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## 8. Кадры и анимация
|
|
532
|
+
|
|
533
|
+
### `w.frame(layer="main", keep=False)`
|
|
534
|
+
|
|
535
|
+
Контекст кадра для `with` или `async with`. Даёт картинку `f` (с теми же методами рисования), в которой вы рисуете, а при выходе из блока она целиком заменяет содержимое слоя.
|
|
536
|
+
|
|
537
|
+
- `layer`: на какой слой выводить (имя или объект слоя). Слой создаётся автоматически, если его нет.
|
|
538
|
+
- `keep=False`: кадр стартует **чистым**. `keep=True`: кадр стартует с копией текущего содержимого слоя, и вы дорисовываете поверх.
|
|
539
|
+
- Если внутри блока произошла ошибка, кадр **отбрасывается**, на экране остаётся прошлый.
|
|
540
|
+
- `f.show()` показывает кадр сразу, не дожидаясь конца блока.
|
|
541
|
+
- В RGB режиме кадр начинает с цвета слоя; внутри можно менять `f.color`.
|
|
542
|
+
|
|
543
|
+
```python
|
|
544
|
+
with w.frame() as f: # чистый кадр
|
|
545
|
+
f.circle(16, 16, 10)
|
|
546
|
+
|
|
547
|
+
with w.frame(keep=True) as f: # дорисовать к тому, что уже есть
|
|
548
|
+
f.clear_pixel(16, 16)
|
|
549
|
+
|
|
550
|
+
with w.frame("hud") as f: # на слой "hud"
|
|
551
|
+
f.text(1, 1, "LV 3")
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### `w.loop(fps=25, layer="main", keep=False, frames=None)`
|
|
555
|
+
|
|
556
|
+
Цикл по кадрам: каждый шаг `for` даёт чистый кадр `f`, а при переходе к следующему шагу он показывается. Темп держится сам, `time.sleep` не нужен.
|
|
557
|
+
|
|
558
|
+
- `fps`: кадров в секунду (больше 0).
|
|
559
|
+
- `frames`: сколько кадров, потом цикл заканчивается сам. `None`: бесконечно, пока окно открыто (или пока вы не сделаете `break`).
|
|
560
|
+
|
|
561
|
+
```python
|
|
562
|
+
for step, f in enumerate(w.loop(fps=25, frames=25)):
|
|
563
|
+
f.rect(step, step, 8, 8)
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Кадр, нарисованный перед `break`, всё равно показывается.
|
|
567
|
+
|
|
568
|
+
### `w.play(frames, fps=10, repeat=1, layer="main")`
|
|
569
|
+
|
|
570
|
+
Проигрывает готовый список кадров. Кадры можно задавать ASCII-строками, массивами `numpy` (в том числе `(H, W, 3)` в RGB режиме) или `Image`.
|
|
571
|
+
|
|
572
|
+
- `repeat`: сколько раз проиграть список. `None`: бесконечно, пока окно открыто.
|
|
573
|
+
|
|
574
|
+
```python
|
|
575
|
+
w.play(["#.#.\n.#.#", ".#.#\n#.#."], fps=4, repeat=None)
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Асинхронный режим
|
|
579
|
+
|
|
580
|
+
Рисующие методы (`set_pixel`, `rect`, `text` и другие) мгновенные, поэтому в `async`-коде их можно вызывать как обычно, ничего ждать не нужно. Асинхронные версии есть только у того, что действительно ждёт:
|
|
581
|
+
|
|
582
|
+
| Синхронно | Асинхронно |
|
|
583
|
+
|---|---|
|
|
584
|
+
| `with w.frame() as f:` | `async with w.frame() as f:` |
|
|
585
|
+
| `for f in w.loop(...)` | `async for f in w.aloop(...)` |
|
|
586
|
+
| `w.play(...)` | `await w.aplay(...)` |
|
|
587
|
+
| `w.flush()` | `await w.aflush()` |
|
|
588
|
+
|
|
589
|
+
Если выходите из `aloop` через `break`, вызовите `f.show()` на последнем кадре.
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
593
|
+
## 9. Слои
|
|
594
|
+
|
|
595
|
+
Слои накладываются друг на друга в порядке создания (последний сверху). Всё, что вы рисуете прямо на `w`, лежит в слое `"main"`, он есть всегда.
|
|
596
|
+
|
|
597
|
+
### `w.layer(name="main", blend=None)`
|
|
598
|
+
|
|
599
|
+
Возвращает слой с таким именем, создавая его, если нет. На слое работают все методы рисования из разделов 6 и 7.
|
|
600
|
+
|
|
601
|
+
- `blend`: как слой накладывается на нижние.
|
|
602
|
+
- `"or"` (по умолчанию): пиксели слоя, которые «есть» (не чёрные), ложатся поверх нижних, а там, где слой пуст, виден нижний. В чёрно-белом режиме это объединение.
|
|
603
|
+
- `"xor"`: слой переключает пиксели нижних слоёв (в цвете: XOR цветов).
|
|
604
|
+
|
|
605
|
+
```python
|
|
606
|
+
bg = w.layer("bg")
|
|
607
|
+
hud = w.layer("hud")
|
|
608
|
+
bg.rect(0, 14, 32, 18)
|
|
609
|
+
hud.text(1, 1, "SCORE")
|
|
610
|
+
hud.clear_pixel() # надпись пропала, фон остался
|
|
611
|
+
hud.visible = False # спрятать, не стирая
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
| У слоя | Описание |
|
|
615
|
+
|---|---|
|
|
616
|
+
| `layer.name` | Имя |
|
|
617
|
+
| `layer.visible` | `True`/`False`: показать или спрятать |
|
|
618
|
+
| `layer.blend` | `"or"` или `"xor"` |
|
|
619
|
+
| `layer.color` | Текущий цвет слоя (RGB) |
|
|
620
|
+
|
|
621
|
+
### `w.remove_layer(name)`
|
|
622
|
+
|
|
623
|
+
Удаляет слой. Слой `"main"` удалить нельзя (`ValueError`).
|
|
624
|
+
|
|
625
|
+
### `w.reset()`
|
|
626
|
+
|
|
627
|
+
Удаляет все слои, кроме `"main"`, очищает его и возвращает ему белый цвет. Окно становится пустым, как после запуска (яркость и цвета ч/б окна не меняются). `w.clear_pixel()` очищает только слой `"main"`, остальные слои не трогает.
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
## 10. Image: картинка в памяти
|
|
632
|
+
|
|
633
|
+
`Image` — сетка пикселей с теми же методами рисования, что и у окна, но не связанная с экраном. Нарисовать на ней можно сколько угодно, на экране это появится, только если вы вставите её в окно через `paste`.
|
|
634
|
+
|
|
635
|
+
```python
|
|
636
|
+
from lib import Image
|
|
637
|
+
|
|
638
|
+
badge = Image(12, 12)
|
|
639
|
+
badge.circle(5, 5, 5, fill=True)
|
|
640
|
+
badge.text(2, 3, "A", mode="clear")
|
|
641
|
+
|
|
642
|
+
w.paste(badge, 10, 4)
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### Создание
|
|
646
|
+
|
|
647
|
+
| Способ | Описание |
|
|
648
|
+
|---|---|
|
|
649
|
+
| `Image(width, height, rgb=False)` | Пустая картинка. `rgb=True` — цветная |
|
|
650
|
+
| `Image.from_matrix(matrix)` | Из массива `numpy` или списка списков. 2D даёт чёрно-белую картинку, 3D `(H, W, 3)` — цветную; размер берётся из матрицы |
|
|
651
|
+
| `Image.from_ascii(art)` | Чёрно-белая из рисунка символами: `#`, `X`, `1`, `@` — включено, всё остальное (лучше `.`) — выключено |
|
|
652
|
+
| `Image.from_ascii(art, palette={...})` | Цветная: словарь «символ → цвет», символы вне словаря пустые |
|
|
653
|
+
|
|
654
|
+
```python
|
|
655
|
+
SHIP = Image.from_ascii("""
|
|
656
|
+
..#..
|
|
657
|
+
.###.
|
|
658
|
+
#####
|
|
659
|
+
#.#.#
|
|
660
|
+
""")
|
|
661
|
+
|
|
662
|
+
APPLE = Image.from_ascii("""
|
|
663
|
+
..g..
|
|
664
|
+
.rrr.
|
|
665
|
+
rrrrr
|
|
666
|
+
.rrr.
|
|
667
|
+
""", palette={"r": "red", "g": "lime"})
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
Пустые строки в начале и конце и общий отступ слева убираются.
|
|
671
|
+
|
|
672
|
+
### Чтение и отладка
|
|
673
|
+
|
|
674
|
+
| Что | Описание |
|
|
675
|
+
|---|---|
|
|
676
|
+
| `image.width`, `image.height`, `image.rgb` | Размер и режим |
|
|
677
|
+
| `image.color` | Текущий цвет рисования (только `rgb=True`) |
|
|
678
|
+
| `image.array` | Копия содержимого: чёрно-белая даёт 0 и 1 формы `(H, W)`, цветная — `(H, W, 3)` |
|
|
679
|
+
| `image.get_pixel(x, y)`, `image[x, y]` | Чтение пикселя (`0/1` или `(r, g, b)`) |
|
|
680
|
+
| `print(image)`, `image.to_ascii(on="#", off=".")` | Картинка текстом |
|
|
681
|
+
| `image.save_png(path, scale=10)` | Сохранить в PNG с увеличением |
|
|
682
|
+
|
|
683
|
+
### `text_width(text, spacing=1)`
|
|
684
|
+
|
|
685
|
+
Отдельная функция: ширина текста в пикселях (для многострочного — самой длинной строки). Нужна для центрирования и бегущей строки.
|
|
686
|
+
|
|
687
|
+
```python
|
|
688
|
+
from lib import text_width
|
|
689
|
+
|
|
690
|
+
x = (32 - text_width("OK")) // 2
|
|
691
|
+
w.text(x, 13, "OK")
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
696
|
+
## 11. Синхронизация, отладка, закрытие
|
|
697
|
+
|
|
698
|
+
### Синхронизация с окном
|
|
699
|
+
|
|
700
|
+
| Метод | Описание |
|
|
701
|
+
|---|---|
|
|
702
|
+
| `w.flush(timeout=1.0)` | Ждёт, пока всё, что вы нарисовали до этого вызова, появится в окне. `True` — дождались (или окно закрыто), `False` — не успели за `timeout` секунд. Нужна, когда скрипт вот-вот завершится, а последний кадр должен успеть показаться |
|
|
703
|
+
| `await w.aflush(timeout=1.0)` | То же без блокировки event loop |
|
|
704
|
+
| `w.wait(timeout=None)` | Блокирует, пока окно не закроют (Esc, крестик, `close()`). Удобно в конце скрипта |
|
|
705
|
+
| `w.close()` | Закрывает окно. После этого вызовы рисования ничего не делают и не падают |
|
|
706
|
+
|
|
707
|
+
Закрытое окно заново открыть нельзя: создайте новый `PixelWidget`.
|
|
708
|
+
|
|
709
|
+
### Отладка
|
|
710
|
+
|
|
711
|
+
| Метод | Описание |
|
|
712
|
+
|---|---|
|
|
713
|
+
| `print(w)`, `w.to_ascii(on="#", off=".")` | Картинка текстом: строка на каждую строку сетки (цветные пиксели выводятся как «включённые») |
|
|
714
|
+
| `w.get_frame()` | Копия того, что сейчас на экране (все видимые слои склеены): чёрно-белая даёт 0 и 1 формы `(H, W)`, RGB — `(H, W, 3)` |
|
|
715
|
+
| `w.get_pixel(x, y)`, `w[x, y]` | Один пиксель |
|
|
716
|
+
| `w.save_png(path, scale=None)` | Сохранить в PNG ровно то, что видно в окне (с цветами и яркостью). `scale`: во сколько раз увеличить (по умолчанию ширина окна / ширина сетки) |
|
|
717
|
+
| `w.snapshot()` | Запомнить состояние всех слоёв (содержимое, видимость, режим наложения, цвет) |
|
|
718
|
+
| `w.restore(snapshot)` | Вернуть всё как было при `snapshot()`; лишние слои удаляются |
|
|
719
|
+
|
|
720
|
+
```python
|
|
721
|
+
saved = w.snapshot()
|
|
722
|
+
w.text(1, 13, "WAIT")
|
|
723
|
+
time.sleep(2)
|
|
724
|
+
w.restore(saved)
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
---
|
|
728
|
+
|
|
729
|
+
## 12. Ошибки
|
|
730
|
+
|
|
731
|
+
Библиотека проверяет аргументы сразу и объясняет, что не так. Если ошибка случилась, картинка остаётся как была (частично применённых действий нет).
|
|
732
|
+
|
|
733
|
+
| Ситуация | Исключение |
|
|
734
|
+
|---|---|
|
|
735
|
+
| Матрица не того размера, не 2D/3D, пустая, с `NaN` | `ValueError` |
|
|
736
|
+
| Матрица из строк или объектов; цветная матрица из дробных чисел | `TypeError` |
|
|
737
|
+
| Цветная матрица не формы `(H, W, 3)`, значения вне 0..255 | `ValueError` |
|
|
738
|
+
| Цветная матрица или картинка в чёрно-белом режиме | `ValueError` |
|
|
739
|
+
| `color=`, `w.color = ...`, `gradient()` в чёрно-белом режиме | `ValueError` |
|
|
740
|
+
| `on_color` / `off_color` вместе с `rgb=True` | `ValueError` |
|
|
741
|
+
| Неизвестное имя цвета, плохой hex, канал вне 0..255, кортеж не из 3 чисел | `ValueError` |
|
|
742
|
+
| Цвет неподходящего типа (`True`, объект, дробный канал) | `TypeError` |
|
|
743
|
+
| Одна координата вместо двух: `set_pixel(3)`, `set_pixel(None, 3)` | `TypeError` |
|
|
744
|
+
| Дробная координата с остатком (`1.5`), строка вместо числа | `TypeError` |
|
|
745
|
+
| Отрицательные `w`, `h`, `r`, `spacing`, `frames`, `repeat` | `ValueError` |
|
|
746
|
+
| Неизвестный `mode`, `blend`, `anchor`, `direction` | `ValueError` |
|
|
747
|
+
| `text` — не строка | `TypeError` |
|
|
748
|
+
| Пиксель за границей при `strict=True` | `IndexError` |
|
|
749
|
+
| `get_pixel` / `w[x, y]` за границей | `IndexError` |
|
|
750
|
+
| Индекс не из двух частей: `w[3]`, срез со `step` | `TypeError` / `ValueError` |
|
|
751
|
+
| `brightness`, `s`, `v`, `t` вне 0..1 | `ValueError` |
|
|
752
|
+
| Удаление слоя `"main"`, пустое имя слоя | `ValueError` |
|
|
753
|
+
| `fps` не больше 0 | `ValueError` |
|
|
754
|
+
| `transparent=False` вместе с `mode` не `"set"` | `ValueError` |
|
|
755
|
+
| Поток окна упал (причина печатается в консоль) | `RuntimeError` при следующем вызове |
|
|
756
|
+
|
|
757
|
+
Пример:
|
|
758
|
+
|
|
759
|
+
```python
|
|
760
|
+
try:
|
|
761
|
+
w.set_pixel(np.ones((10, 10)))
|
|
762
|
+
except ValueError as error:
|
|
763
|
+
print(error) # matrix shape (10, 10) does not match the grid: expected (32, 32) = (rows, columns)
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
---
|
|
767
|
+
|
|
768
|
+
## 13. Демо-примеры
|
|
769
|
+
|
|
770
|
+
Каждый пример можно скопировать в отдельный файл рядом с `lib.py` и запустить. Примеры 1–10 работают в чёрно-белом режиме, 11–15 показывают цвет.
|
|
771
|
+
|
|
772
|
+
### Демо 1. Рамка, маска и бегущий квадрат (синхронный код)
|
|
773
|
+
|
|
774
|
+
```python
|
|
775
|
+
import time
|
|
776
|
+
|
|
777
|
+
import numpy as np
|
|
778
|
+
|
|
779
|
+
from lib import PixelWidget
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
def main():
|
|
783
|
+
w = PixelWidget(title="Sync Widget")
|
|
784
|
+
time.sleep(0.5)
|
|
785
|
+
|
|
786
|
+
w.rect(0, 0, 32, 32, fill=False)
|
|
787
|
+
time.sleep(1.0)
|
|
788
|
+
w.clear_pixel()
|
|
789
|
+
|
|
790
|
+
pattern = np.fromfunction(lambda r, c: (r + c) % 4 == 0, (32, 32), dtype=int)
|
|
791
|
+
w.set_pixel(pattern)
|
|
792
|
+
time.sleep(2.0)
|
|
793
|
+
w.clear_pixel(pattern)
|
|
794
|
+
time.sleep(1.0)
|
|
795
|
+
|
|
796
|
+
for pos, f in enumerate(w.loop(fps=25, frames=25)):
|
|
797
|
+
f.rect(pos, pos, 8, 8)
|
|
798
|
+
|
|
799
|
+
w.flush()
|
|
800
|
+
time.sleep(2.0)
|
|
801
|
+
w.clear_pixel()
|
|
802
|
+
w.flush()
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
if __name__ == "__main__":
|
|
806
|
+
main()
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
Что здесь происходит: рамка рисуется прямо на окне; маска включается через `set_pixel` и гасится через `clear_pixel`; квадрат бежит по диагонали 25 кадров, темп держит `loop`. Квадрат 8×8 на сетке 32×32 помещается при позициях `0..24`, поэтому `frames=25`. В конце `flush()` гарантирует, что последний кадр успел показаться до выхода из скрипта.
|
|
810
|
+
|
|
811
|
+
### Демо 2. То же в асинхронном коде
|
|
812
|
+
|
|
813
|
+
```python
|
|
814
|
+
import asyncio
|
|
815
|
+
|
|
816
|
+
from lib import PixelWidget
|
|
817
|
+
|
|
818
|
+
|
|
819
|
+
async def main():
|
|
820
|
+
w = PixelWidget(title="Async Widget")
|
|
821
|
+
await asyncio.sleep(0.5)
|
|
822
|
+
|
|
823
|
+
pos = 0
|
|
824
|
+
async for f in w.aloop(fps=25, frames=25):
|
|
825
|
+
f.rect(pos, pos, 8, 8)
|
|
826
|
+
pos += 1
|
|
827
|
+
|
|
828
|
+
await w.aflush()
|
|
829
|
+
await asyncio.sleep(1.0)
|
|
830
|
+
|
|
831
|
+
w.text(1, 1, "ASYNC")
|
|
832
|
+
w.line(0, 8, 31, 8)
|
|
833
|
+
await w.aflush()
|
|
834
|
+
await asyncio.sleep(2.0)
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
if __name__ == "__main__":
|
|
838
|
+
asyncio.run(main())
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
Рисующие методы (`w.text`, `w.line`) вызываются как обычно, асинхронно ведут себя только `aloop` и `aflush`. Блокирующего `time.sleep` внутри `async`-кода быть не должно, только `asyncio.sleep`.
|
|
842
|
+
|
|
843
|
+
### Демо 3. Фигуры и режимы рисования
|
|
844
|
+
|
|
845
|
+
```python
|
|
846
|
+
import time
|
|
847
|
+
|
|
848
|
+
from lib import PixelWidget
|
|
849
|
+
|
|
850
|
+
w = PixelWidget(title="Shapes")
|
|
851
|
+
|
|
852
|
+
with w.frame() as f:
|
|
853
|
+
f.rect(1, 1, 30, 30, fill=False)
|
|
854
|
+
f.circle(16, 16, 10)
|
|
855
|
+
f.line(1, 1, 30, 30)
|
|
856
|
+
f.circle(16, 16, 4, fill=True, mode="xor")
|
|
857
|
+
w.flush()
|
|
858
|
+
time.sleep(2)
|
|
859
|
+
|
|
860
|
+
w.xor_pixel()
|
|
861
|
+
w.flush()
|
|
862
|
+
time.sleep(2)
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
Второй шаг `w.xor_pixel()` без аргументов инвертирует всю картинку (негатив). Обратите внимание на маленький круг в центре: он нарисован с `xor`, поэтому в местах, где под ним уже были линии, пиксели погасли.
|
|
866
|
+
|
|
867
|
+
### Демо 4. Матрицы: маски и «Жизнь»
|
|
868
|
+
|
|
869
|
+
```python
|
|
870
|
+
import time
|
|
871
|
+
|
|
872
|
+
import numpy as np
|
|
873
|
+
|
|
874
|
+
from lib import PixelWidget
|
|
875
|
+
|
|
876
|
+
w = PixelWidget(title="Matrices")
|
|
877
|
+
|
|
878
|
+
rows = np.fromfunction(lambda r, c: r % 2 == 0, (32, 32), dtype=int)
|
|
879
|
+
columns = np.fromfunction(lambda r, c: c % 2 == 0, (32, 32), dtype=int)
|
|
880
|
+
|
|
881
|
+
w.set_pixel(rows)
|
|
882
|
+
w.flush()
|
|
883
|
+
time.sleep(1)
|
|
884
|
+
w.xor_pixel(columns)
|
|
885
|
+
w.flush()
|
|
886
|
+
time.sleep(1)
|
|
887
|
+
w.clear_pixel(rows)
|
|
888
|
+
w.flush()
|
|
889
|
+
time.sleep(1)
|
|
890
|
+
|
|
891
|
+
rng = np.random.default_rng(1)
|
|
892
|
+
cells = rng.random((32, 32)) > 0.7
|
|
893
|
+
for step, f in enumerate(w.loop(fps=10, frames=40)):
|
|
894
|
+
neighbors = sum(np.roll(np.roll(cells, dy, 0), dx, 1)
|
|
895
|
+
for dy in (-1, 0, 1) for dx in (-1, 0, 1) if (dy, dx) != (0, 0))
|
|
896
|
+
cells = (neighbors == 3) | (cells & (neighbors == 2))
|
|
897
|
+
f.replace(cells)
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
Первая часть показывает три действия с масками: `set_pixel` включает полосы, `xor_pixel` переключает столбцы, `clear_pixel` гасит полосы. Вторая часть — игра «Жизнь»: новое состояние считается обычным `numpy`, а `f.replace(cells)` выводит его целиком. Матрицы везде имеют форму `(32, 32)`, как и сетка.
|
|
901
|
+
|
|
902
|
+
### Демо 5. Фон и HUD на разных слоях
|
|
903
|
+
|
|
904
|
+
```python
|
|
905
|
+
import time
|
|
906
|
+
|
|
907
|
+
from lib import PixelWidget
|
|
908
|
+
|
|
909
|
+
w = PixelWidget(title="Layers")
|
|
910
|
+
|
|
911
|
+
bg = w.layer("bg")
|
|
912
|
+
hud = w.layer("hud")
|
|
913
|
+
|
|
914
|
+
bg.rect(0, 12, 32, 20)
|
|
915
|
+
hud.text(1, 1, "SCORE")
|
|
916
|
+
hud.text(1, 7, "00042")
|
|
917
|
+
w.flush()
|
|
918
|
+
time.sleep(2)
|
|
919
|
+
|
|
920
|
+
hud.clear_pixel()
|
|
921
|
+
w.flush()
|
|
922
|
+
time.sleep(2)
|
|
923
|
+
|
|
924
|
+
w.reset()
|
|
925
|
+
w.flush()
|
|
926
|
+
```
|
|
927
|
+
|
|
928
|
+
После `hud.clear_pixel()` надпись исчезает, а фон остаётся, потому что они на разных слоях. `w.reset()` убирает все слои и очищает окно.
|
|
929
|
+
|
|
930
|
+
### Демо 6. Image и бегущая строка
|
|
931
|
+
|
|
932
|
+
```python
|
|
933
|
+
import time
|
|
934
|
+
|
|
935
|
+
from lib import Image, PixelWidget, text_width
|
|
936
|
+
|
|
937
|
+
SHIP = Image.from_ascii("""
|
|
938
|
+
..#..
|
|
939
|
+
.###.
|
|
940
|
+
#####
|
|
941
|
+
#.#.#
|
|
942
|
+
""")
|
|
943
|
+
|
|
944
|
+
w = PixelWidget(title="Ticker")
|
|
945
|
+
|
|
946
|
+
ship = w.layer("ship")
|
|
947
|
+
ship.paste(SHIP, 13, 24)
|
|
948
|
+
|
|
949
|
+
message = "HELLO WIDGET "
|
|
950
|
+
for step in range(text_width(message) + 32):
|
|
951
|
+
with w.frame("ticker") as f:
|
|
952
|
+
f.text(32 - step, 4, message)
|
|
953
|
+
time.sleep(0.04)
|
|
954
|
+
```
|
|
955
|
+
|
|
956
|
+
Корабль нарисован один раз на своём слое, и бегущая строка его не стирает. Текст на каждом шаге выезжает слева на один пиксель; части за краем экрана обрезаются сами.
|
|
957
|
+
|
|
958
|
+
### Демо 7. Анимация из готовых кадров
|
|
959
|
+
|
|
960
|
+
```python
|
|
961
|
+
from lib import PixelWidget
|
|
962
|
+
|
|
963
|
+
w = PixelWidget(title="Frames")
|
|
964
|
+
|
|
965
|
+
w.play([
|
|
966
|
+
"""
|
|
967
|
+
#.#.
|
|
968
|
+
.#.#
|
|
969
|
+
""",
|
|
970
|
+
"""
|
|
971
|
+
.#.#
|
|
972
|
+
#.#.
|
|
973
|
+
""",
|
|
974
|
+
], fps=4, repeat=8)
|
|
975
|
+
```
|
|
976
|
+
|
|
977
|
+
`play` принимает кадры в виде ASCII-строк, массивов `numpy` или `Image`. Для бесконечного цикла поставьте `repeat=None`.
|
|
978
|
+
|
|
979
|
+
### Демо 8. Мигающий курсор через xor
|
|
980
|
+
|
|
981
|
+
```python
|
|
982
|
+
import time
|
|
983
|
+
|
|
984
|
+
from lib import PixelWidget
|
|
985
|
+
|
|
986
|
+
w = PixelWidget(title="Cursor")
|
|
987
|
+
|
|
988
|
+
for _ in range(10):
|
|
989
|
+
w.xor_pixel(5, 5)
|
|
990
|
+
w.flush()
|
|
991
|
+
time.sleep(0.3)
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
Каждый вызов `xor_pixel` переключает пиксель: был включён — гаснет, был выключен — загорается.
|
|
995
|
+
|
|
996
|
+
### Демо 9. Отладка: картинка в консоль, PNG и откат
|
|
997
|
+
|
|
998
|
+
```python
|
|
999
|
+
import time
|
|
1000
|
+
|
|
1001
|
+
from lib import PixelWidget
|
|
1002
|
+
|
|
1003
|
+
w = PixelWidget(title="Debug")
|
|
1004
|
+
|
|
1005
|
+
w.circle(16, 16, 8)
|
|
1006
|
+
w.flush()
|
|
1007
|
+
|
|
1008
|
+
print(w)
|
|
1009
|
+
w.save_png("frame.png")
|
|
1010
|
+
|
|
1011
|
+
saved = w.snapshot()
|
|
1012
|
+
w.clear_pixel()
|
|
1013
|
+
w.flush()
|
|
1014
|
+
time.sleep(1)
|
|
1015
|
+
|
|
1016
|
+
w.restore(saved)
|
|
1017
|
+
w.flush()
|
|
1018
|
+
time.sleep(1)
|
|
1019
|
+
```
|
|
1020
|
+
|
|
1021
|
+
`print(w)` показывает, что именно нарисовано, текстом. `save_png` сохраняет картинку файлом. `snapshot` и `restore` откатывают окно к сохранённому состоянию.
|
|
1022
|
+
|
|
1023
|
+
### Демо 10. Широкое окно в углу экрана
|
|
1024
|
+
|
|
1025
|
+
```python
|
|
1026
|
+
from lib import PixelWidget, text_width
|
|
1027
|
+
|
|
1028
|
+
w = PixelWidget(width=64, height=16, window_size=(256, 64), title="Wide")
|
|
1029
|
+
|
|
1030
|
+
message = "THE WINDOW STAYS IN THE CORNER "
|
|
1031
|
+
for step in range(text_width(message) + 64):
|
|
1032
|
+
with w.frame() as f:
|
|
1033
|
+
f.text(64 - step, 5, message)
|
|
1034
|
+
f.rect(0, 0, 64, 16, fill=False)
|
|
1035
|
+
w.flush()
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
Окно 256×64 для сетки 64×16 (каждый пиксель в 4 раза крупнее) прижимается правым нижним углом к тому же месту экрана, что и окно по умолчанию. Если поменять `window_size`, угол останется на месте.
|
|
1039
|
+
|
|
1040
|
+
### Демо 11. Цвет: `color=`, текущий цвет, индексация
|
|
1041
|
+
|
|
1042
|
+
```python
|
|
1043
|
+
import time
|
|
1044
|
+
|
|
1045
|
+
from lib import PixelWidget, hsv, mix
|
|
1046
|
+
|
|
1047
|
+
w = PixelWidget(rgb=True, title="Colors")
|
|
1048
|
+
|
|
1049
|
+
w.rect(0, 0, 10, 10, color="red")
|
|
1050
|
+
w.circle(16, 16, 8, fill=True, color="#00ff88")
|
|
1051
|
+
w.line(0, 31, 31, 0, color=(255, 128, 0))
|
|
1052
|
+
w.set_pixel(5, 5, color=mix("red", "blue", 0.5))
|
|
1053
|
+
|
|
1054
|
+
w.color = "yellow"
|
|
1055
|
+
w.text(1, 22, "HI")
|
|
1056
|
+
w.text(14, 22, "OK", color="cyan")
|
|
1057
|
+
|
|
1058
|
+
for x in range(32):
|
|
1059
|
+
w[x, 31] = hsv(x * 360 / 32)
|
|
1060
|
+
|
|
1061
|
+
w[0:3, 12:15] = "white"
|
|
1062
|
+
print(w.get_pixel(5, 5), w[0, 0], w[20, 31])
|
|
1063
|
+
|
|
1064
|
+
w.flush()
|
|
1065
|
+
time.sleep(4)
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
Что здесь происходит: фигуры рисуются разными способами указания цвета (имя, hex, кортеж, `mix`); после `w.color = "yellow"` цвет действует до следующей смены, а `color="cyan"` перекрывает его на один вызов; нижняя строка закрашивается радугой через индексацию `w[x, y]`; `print` показывает цвета как кортежи `(r, g, b)`.
|
|
1069
|
+
|
|
1070
|
+
### Демо 12. Радуга и градиенты
|
|
1071
|
+
|
|
1072
|
+
```python
|
|
1073
|
+
import time
|
|
1074
|
+
|
|
1075
|
+
from lib import PixelWidget, hsv
|
|
1076
|
+
|
|
1077
|
+
w = PixelWidget(rgb=True, title="Rainbow")
|
|
1078
|
+
|
|
1079
|
+
w.gradient(0, 0, 32, 8, "red", "blue")
|
|
1080
|
+
w.gradient(0, 10, 32, 6, "black", "white")
|
|
1081
|
+
w.gradient(0, 18, 32, 6, "yellow", "magenta", direction="horizontal")
|
|
1082
|
+
w.flush()
|
|
1083
|
+
time.sleep(2)
|
|
1084
|
+
|
|
1085
|
+
for shift, f in enumerate(w.loop(fps=30, frames=90)):
|
|
1086
|
+
for x in range(32):
|
|
1087
|
+
f.rect(x, 0, 1, 32, color=hsv((x * 12 + shift * 6) % 360))
|
|
1088
|
+
f.circle(16, 16, 6, fill=True, mode="clear")
|
|
1089
|
+
f.circle(16, 16, 6, color="white")
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
Первая часть показывает три градиента (по умолчанию слева направо). Вторая — бегущая радуга: на каждом кадре цвет столбца зависит от `x` и номера кадра, в центре вырезается круг режимом `clear` и обводится белой окружностью.
|
|
1093
|
+
|
|
1094
|
+
### Демо 13. Цветной спрайт на градиентном фоне (слои)
|
|
1095
|
+
|
|
1096
|
+
```python
|
|
1097
|
+
import time
|
|
1098
|
+
|
|
1099
|
+
from lib import Image, PixelWidget
|
|
1100
|
+
|
|
1101
|
+
APPLE = Image.from_ascii("""
|
|
1102
|
+
..g..
|
|
1103
|
+
.rrr.
|
|
1104
|
+
rrrrr
|
|
1105
|
+
rrrrr
|
|
1106
|
+
.rrr.
|
|
1107
|
+
""", palette={"r": "red", "g": "lime"})
|
|
1108
|
+
|
|
1109
|
+
w = PixelWidget(rgb=True, title="Sprite")
|
|
1110
|
+
|
|
1111
|
+
background = w.layer("background")
|
|
1112
|
+
background.gradient(0, 0, 32, 32, "navy", "teal", direction="vertical")
|
|
1113
|
+
|
|
1114
|
+
sprites = w.layer("sprites")
|
|
1115
|
+
for step in range(28):
|
|
1116
|
+
sprites.clear_pixel()
|
|
1117
|
+
sprites.paste(APPLE, step, 2 + step // 2)
|
|
1118
|
+
w.flush()
|
|
1119
|
+
time.sleep(0.05)
|
|
1120
|
+
|
|
1121
|
+
hud = w.layer("hud")
|
|
1122
|
+
hud.text(4, 24, "YUM", color="yellow")
|
|
1123
|
+
w.flush()
|
|
1124
|
+
time.sleep(2)
|
|
1125
|
+
```
|
|
1126
|
+
|
|
1127
|
+
Слои идут в порядке создания: `background` (фон), `sprites` (яблоко), `hud` (надпись). Фон не стирается, когда яблоко движется по своему слою. Чёрные (пустые) пиксели спрайта прозрачны, поэтому сквозь них виден градиент. Надпись лежит на самом верхнем слое. Если бы вы нарисовали её прямо на `w` (слой `main`, который создан первым и лежит ниже всех), её закрыл бы непрозрачный градиент.
|
|
1128
|
+
|
|
1129
|
+
### Демо 14. Чёрно-белое окно с цветом показа и яркостью
|
|
1130
|
+
|
|
1131
|
+
```python
|
|
1132
|
+
import time
|
|
1133
|
+
|
|
1134
|
+
from lib import PixelWidget
|
|
1135
|
+
|
|
1136
|
+
w = PixelWidget(on_color="lime", off_color="#001000", title="Terminal")
|
|
1137
|
+
|
|
1138
|
+
w.rect(0, 0, 32, 32, fill=False)
|
|
1139
|
+
w.text(3, 4, "READY")
|
|
1140
|
+
w.text(3, 12, "> OK")
|
|
1141
|
+
w.flush()
|
|
1142
|
+
time.sleep(2)
|
|
1143
|
+
|
|
1144
|
+
w.on_color = "orange"
|
|
1145
|
+
w.off_color = "#100800"
|
|
1146
|
+
w.flush()
|
|
1147
|
+
time.sleep(2)
|
|
1148
|
+
|
|
1149
|
+
for level in range(10, -1, -1):
|
|
1150
|
+
w.brightness = level / 10
|
|
1151
|
+
time.sleep(0.15)
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
Рисуем обычным чёрно-белым способом, а показываем «зелёным терминалом», потом «оранжевым», потом плавно гасим через `brightness`. `color=` здесь по-прежнему недоступен: это чёрно-белое окно, меняется только то, как оно показано.
|
|
1155
|
+
|
|
1156
|
+
### Демо 15. Цветные матрицы: плазма
|
|
1157
|
+
|
|
1158
|
+
```python
|
|
1159
|
+
import numpy as np
|
|
1160
|
+
|
|
1161
|
+
from lib import PixelWidget
|
|
1162
|
+
|
|
1163
|
+
w = PixelWidget(rgb=True, title="Plasma")
|
|
1164
|
+
|
|
1165
|
+
y, x = np.mgrid[0:32, 0:32]
|
|
1166
|
+
|
|
1167
|
+
for t, f in enumerate(w.loop(fps=20, frames=120)):
|
|
1168
|
+
v = np.sin(x / 4 + t / 5) + np.sin(y / 5 - t / 7) + np.sin((x + y) / 6 + t / 9)
|
|
1169
|
+
red = 127.5 * (1 + np.sin(v))
|
|
1170
|
+
green = 127.5 * (1 + np.sin(v + 2.1))
|
|
1171
|
+
blue = 127.5 * (1 + np.sin(v + 4.2))
|
|
1172
|
+
f.replace(np.stack([red, green, blue], axis=2).astype(np.uint8))
|
|
1173
|
+
```
|
|
1174
|
+
|
|
1175
|
+
Кадр вычисляется целиком обычным `numpy` как массив формы `(32, 32, 3)` с целыми 0..255, а `f.replace(...)` выводит его в окно. Так же можно показывать любые изображения, подготовленные `numpy` или OpenCV (не забудьте перевести BGR в RGB: `frame[:, :, ::-1]`).
|
|
1176
|
+
|
|
1177
|
+
---
|
|
1178
|
+
|
|
1179
|
+
## 14. Частые проблемы и ограничения
|
|
1180
|
+
|
|
1181
|
+
| Симптом | Причина и решение |
|
|
1182
|
+
|---|---|
|
|
1183
|
+
| Окно исчезает сразу после запуска | Окно живёт, пока работает программа. Добавьте в конец `w.wait()` или `time.sleep(...)` |
|
|
1184
|
+
| Последний кадр не успел показаться | Вызовите `w.flush()` перед завершением скрипта |
|
|
1185
|
+
| Окно не там, где ожидалось | Посмотрите `w.position` (левый верхний угол окна). Если по `position` нет монитора, используется `fallback_position`. Проверьте `anchor`: он определяет, какой угол окна стоит в точке `position` |
|
|
1186
|
+
| Окно смещается при смене размера | Так быть не должно: положение считается от угла (`anchor`). Если у вас своя точка `position`, убедитесь, что она указывает на нужный угол, а не на левый верхний |
|
|
1187
|
+
| Картинка мерцает при анимации | Рисуйте через `with w.frame()` или `w.loop()`, а не серией отдельных вызовов с очисткой посередине |
|
|
1188
|
+
| `ValueError: color= is available only with rgb=True` | Цвет есть только в RGB режиме: создайте окно как `PixelWidget(rgb=True)`. Для «цветного показа» ч/б окна используйте `on_color` / `off_color` |
|
|
1189
|
+
| Нарисованный чёрным цветом пиксель «ничего не делает» | В RGB режиме чёрный значит «пусто». Чтобы стереть, используйте `clear_pixel` или `mode="clear"`; чёрный на верхнем слое прозрачен |
|
|
1190
|
+
| Цвета перепутаны (красный стал синим) | Библиотека использует RGB. Если массив пришёл из OpenCV (BGR), переверните каналы: `frame[:, :, ::-1]` |
|
|
1191
|
+
| `set_pixel(матрица)` ничего не стирает | Так задумано: `set_pixel` только включает. Для замены картинки целиком используйте `replace(matrix)`, для стирания — `clear_pixel(mask)` |
|
|
1192
|
+
| `ValueError: matrix shape ... does not match the grid` | Форма матрицы должна быть `(height, width)` (или `(height, width, 3)` для цвета). Проверьте `matrix.shape` и, возможно, перепутанные оси |
|
|
1193
|
+
| `TypeError: an RGB matrix must contain integers 0..255` | Цветную матрицу нужно привести к целым: `frame.astype(np.uint8)` |
|
|
1194
|
+
| `w[3, 4]` вернул не тот пиксель | В индексации первым идёт `x` (столбец), потом `y` (строка), не как в `numpy` |
|
|
1195
|
+
| Фигура «обрезалась» | Так задумано: всё за границами сетки пропускается. Для одиночных пикселей можно включить `strict=True` |
|
|
1196
|
+
| `RuntimeError: the window thread has crashed` | В консоли выше напечатан traceback: там настоящая причина |
|
|
1197
|
+
| Последний кадр перед `break` в `aloop` не виден | Вызовите `f.show()` перед `break` |
|
|
1198
|
+
| Окно не закрывается по Esc | Esc срабатывает, когда окно в фокусе (оно не отбирает фокус само). Кликните по окну или вызовите `w.close()` |
|
|
1199
|
+
|
|
1200
|
+
### Ограничения
|
|
1201
|
+
|
|
1202
|
+
- В одном процессе одновременно живёт одно окно (`pygame` не поддерживает несколько окон сразу). Для нескольких «экранов» используйте слои. После `close()` можно создать новое окно, в том числе другого режима.
|
|
1203
|
+
- Нет полупрозрачности: пиксель либо есть (не чёрный), либо его нет.
|
|
1204
|
+
- Режим (`rgb`) выбирается при создании окна и не переключается на лету.
|
|
1205
|
+
- Позиционирование по мониторам, режим «поверх всех» и скрытие с панели задач работают только в Windows. На других системах окно просто создаётся в указанной точке.
|
|
1206
|
+
- Шрифт один (3×5), без масштаба и без кириллицы.
|