PixelWidget
Маленькое пиксельное окно для Windows на pygame и numpy. Вы рисуете на сетке (по умолчанию 32×32 пикселя), а библиотека показывает её в окне без рамки, которое всегда лежит поверх остальных окон и прижато к углу экрана. Подходит для виджетов, индикаторов, мини-дисплеев, игр на сетке, экспериментов с пиксельной графикой.
Окно бывает двух видов, и функции у них одни и те же:
w = PixelWidget() # чёрно-белое окно (по умолчанию)
w = PixelWidget(rgb=True) # то же окно, но с цветом
Работает и из обычного кода, и из asyncio: синхронному коду asyncio не нужен.
Содержание
- Установка и запуск
- Основные понятия за 3 минуты
- Сравнение с OpenCV (cv2)
- Окно: создание, положение, размер
- Цвет
- Рисование: пиксели и матрицы
- Рисование: фигуры, текст, картинки, градиент
- Кадры и анимация
- Слои
- Image: картинка в памяти
- Синхронизация, отладка, закрытие
- Ошибки
- Демо-примеры
- Частые проблемы и ограничения
1. Установка и запуск
pip install pygame numpy
Положите lib.py рядом со своим скриптом:
from lib import PixelWidget
w = PixelWidget()
w.text(4, 13, "HELLO")
w.wait()
2. Основные понятия за 3 минуты
Сетка и координаты
Картинка состоит из пикселей. В чёрно-белом режиме пиксель либо включён (белый), либо выключен (чёрный). В RGB режиме у пикселя есть цвет, а «выключен» значит чёрный. Точка (0, 0) в левом верхнем углу, x растёт вправо, y растёт вниз. Всё, что выходит за границы сетки, молча обрезается.
(0,0) ──── x ────►
│
y
│
▼
Рисуем прямо на окне
У PixelWidget есть все методы рисования, и они сразу меняют то, что видно в окне:
w = PixelWidget()
w.rect(2, 2, 10, 6)
w.text(1, 20, "HI")
w.set_pixel(0, 0)
Один набор функций для двух режимов
Всё, что работает в чёрно-белом режиме, работает и в RGB. В RGB добавляется необязательный аргумент color=:
w = PixelWidget(rgb=True)
w.rect(2, 2, 10, 6, color="red")
w.text(1, 20, "HI", color="#00ff88")
w.set_pixel(0, 0) # без color рисуется белым
В чёрно-белом режиме color= недоступен и даёт понятную ошибку, а не молча игнорируется.
Три действия: включить, погасить, инвертировать
Это основа всей библиотеки. Для пикселей, матриц и фигур одна и та же идея:
| Действие | Пиксель | Матрица (маска) | Без аргументов | У фигур |
|---|---|---|---|---|
| включить (покрасить) | set_pixel(x, y) |
set_pixel(matrix) |
залить всё | mode="set" |
| погасить | clear_pixel(x, y) |
clear_pixel(matrix) |
очистить всё | mode="clear" |
| инвертировать | xor_pixel(x, y) |
xor_pixel(matrix) |
инвертировать всё | mode="xor" |
Матрица — это массив numpy того же размера, что и сетка. Действие применяется к тем пикселям, где в матрице стоит не ноль, остальные не меняются (подробнее в разделе 6).
Кадр: нарисовать и показать целиком
Если рисуете сложную картинку из нескольких фигур, окно может показать её недорисованной (мерцание). Решение: нарисовать всё в черновике и показать целиком.
with w.frame() as f:
f.rect(2, 2, 8, 8)
f.text(0, 20, "OK")
Внутри блока вы рисуете на f (это пустая картинка, Image, с теми же методами, что у окна). Когда блок закончился, картинка целиком заменяет содержимое окна. Каждый кадр начинается с чистого листа, поэтому очищать экран не нужно.
Слои
Слои как прозрачные плёнки: что нарисовано на нижних, видно сквозь верхние. Типичный случай: фон и надпись поверх него. Стираете надпись, а фон остаётся. Всё, что вы рисуете прямо на w, попадает в слой "main".
Image: картинка в памяти
Image — это такая же сетка пикселей с теми же методами рисования, но не привязанная к окну: что на ней нарисовано, видно, только если вы вставите её в окно (w.paste(image, x, y)). Нужна для заготовок (значок, спрайт) и внутри with w.frame() as f (там f — это Image).
Как работает окно (для любопытных)
Ваш код меняет картинку в памяти мгновенно. Отдельный фоновый поток сам замечает изменение и перерисовывает окно, поэтому вызовы не блокируют вас. Нужно убедиться, что картинка уже на экране? Вызовите w.flush().
3. Сравнение с OpenCV (cv2)
Идея та же: вы вызываете функции рисования фигур. Отличия в деталях:
| cv2 | PixelWidget | Что иначе |
|---|---|---|
cv2.rectangle(img, (x1, y1), (x2, y2), color, thickness) |
w.rect(x, y, w, h, fill, mode, color=) |
Вместо второго угла ширина и высота. Вместо thickness=-1 есть fill=True. Толщина контура 1 пиксель |
cv2.line(img, (x1, y1), (x2, y2), color) |
w.line(x0, y0, x1, y1, mode, color=) |
Координаты отдельными числами, не кортежами |
cv2.circle(img, (cx, cy), r, color, thickness) |
w.circle(cx, cy, r, fill, mode, color=) |
fill=True вместо thickness=-1 |
cv2.putText(img, text, (x, y), font, scale, color) |
w.text(x, y, text, mode, spacing, color=) |
(x, y) — левый верхний угол текста (в cv2 левый нижний). Один шрифт 3×5, без масштаба |
img[y0:y1, x0:x1] = roi |
w.paste(picture, x, y) |
Умеет прозрачность и xor, сам обрезает по краям |
img[y, x] = (b, g, r) |
w[x, y] = (r, g, b) или w.set_pixel(x, y, color=...) |
Порядок: x первым, цвет в RGB (в cv2 строка первой, цвет в BGR) |
img[y, x] |
w[x, y] или w.get_pixel(x, y) |
|
cv2.bitwise_or(img, mask) |
w.set_pixel(mask) |
Маска должна совпадать по размеру с сеткой |
cv2.bitwise_and с инверсией маски |
w.clear_pixel(mask) |
|
cv2.bitwise_xor(img, mask) |
w.xor_pixel(mask) |
|
cv2.bitwise_not(img) |
w.xor_pixel() |
|
img[:] = 0 |
w.clear_pixel() |
|
img[:] = matrix |
w.replace(matrix) |
|
np.roll(img, shift) |
w.scroll(dx, dy, wrap=True) |
|
cv2.addWeighted с затемнением |
w.brightness = 0.5 |
|
cv2.imshow |
with w.frame() as f: ... |
Окно создаётся один раз, кадры обновляют его |
cv2.imwrite |
w.save_png(path) |
|
cv2.waitKey / time.sleep |
w.flush(), w.wait() |
flush() ждёт, пока кадр реально показан |
cv2.cvtColor(..., HSV2BGR) |
hsv(h, s, v) |
Чего в cv2 нет: слои, кадры-транзакции, режим xor у каждой фигуры, встроенный асинхронный режим.
4. Окно: создание, положение, размер
w = PixelWidget(width=32, height=32, window_size=320, title="Pinned Widget",
position=(3840, 1032), fallback_position=(1920, 1008), anchor="bottom_right",
fps=30, always_on_top=True, hide_from_taskbar=True, strict=False,
rgb=False, on_color=None, off_color=None, brightness=1.0)
| Параметр | По умолчанию | Что означает |
|---|---|---|
width, height |
32, 32 |
Размер сетки в пикселях (сколько точек вы рисуете) |
window_size |
320 |
Размер окна на экране в пикселях. Число — квадратное окно, пара (ширина, высота) — прямоугольное |
title |
"Pinned Widget" |
Заголовок окна |
position |
(3840, 1032) |
Точка экрана, к которой прижимается угол окна (какой именно угол — задаёт anchor) |
fallback_position |
(1920, 1008) |
Та же точка, если по position нет монитора (второй монитор отключён) |
anchor |
"bottom_right" |
Какой угол окна стоит в точке position: "top_left", "top_right", "bottom_left", "bottom_right" |
fps |
30 |
Как часто окно проверяет изменения. 30 достаточно |
always_on_top |
True |
Окно всегда поверх остальных. Раз в полсекунды позиция подтверждается заново, свёрнутое окно возвращается |
hide_from_taskbar |
True |
Скрыть окно с панели задач и из Alt+Tab |
strict |
False |
True: set_pixel(x, y) за границей сетки бросает IndexError вместо тихого игнорирования |
rgb |
False |
True: цветное окно, у функций рисования появляется color= |
on_color, off_color |
белый, чёрный | Только для чёрно-белого режима: каким цветом показывать включённые и выключенные пиксели (например, «зелёный экран»). С rgb=True указывать нельзя |
brightness |
1.0 |
Яркость окна от 0 до 1, работает в обоих режимах |
Как окно прижимается к углу (и почему оно не «уезжает» при смене размера)
Положение задаётся углом окна, а не его левым верхним краем. По умолчанию это правый нижний угол: он стоит в точке position, а остальное окно вычисляется от него. Поэтому, как бы вы ни меняли window_size, окно остаётся в том же углу экрана:
PixelWidget(window_size=320) # правый нижний угол окна в (3840, 1032)
PixelWidget(window_size=160) # то же место, окно просто меньше
PixelWidget(window_size=(256, 64)) # прямоугольное окно, угол там же
Нужно прижать к другому углу? Укажите точку этого угла и anchor:
PixelWidget(position=(100, 100), anchor="top_left") # левый верхний угол окна в (100, 100)
PixelWidget(position=(1920, 0), anchor="top_right") # правый верхний угол в (1920, 0)
Если по position нет монитора, окно ставится в fallback_position тем же способом (тем же углом).
Свойства
| Свойство | Что это |
|---|---|
w.width, w.height |
Размер сетки |
w.window_width, w.window_height |
Размер окна на экране |
w.position |
Левый верхний угол окна на экране (уже с учётом anchor и запасной позиции) |
w.rgb |
True, если окно цветное |
w.running |
True, пока окно открыто |
w.layers |
Имена слоёв снизу вверх, например ["main", "hud"] |
w.color |
Текущий цвет рисования (только RGB, см. раздел 5) |
w.on_color, w.off_color |
Цвета включённых и выключенных пикселей (только чёрно-белый режим; в RGB режиме читаются как None) |
w.brightness |
Яркость окна, можно менять на лету |
PixelWidget можно использовать как менеджер контекста: окно закроется само.
with PixelWidget() as w:
w.set_pixel(5, 5)
w.flush()
time.sleep(2)
5. Цвет
Включение
Одним аргументом: PixelWidget(rgb=True). Дальше все функции рисования получают необязательный аргумент color= (только по имени, чтобы его не спутать с координатами).
w = PixelWidget(rgb=True)
w.set_pixel(3, 4, color="red")
w.rect(0, 0, 8, 8, color=(255, 128, 0))
w.line(0, 0, 31, 31, color="#00ff88")
w.circle(16, 16, 5, fill=True, color=0xFF00FF)
Если color= не указан, берётся текущий цвет (по умолчанию белый), поэтому код из чёрно-белого режима работает в RGB без правок.
Как можно задавать цвет
| Способ | Примеры |
|---|---|
| Имя | "red", "green", "blue", "yellow", "cyan", "magenta", "orange", "purple", "pink", "gray", "brown", "teal", "navy", "lime", "white", "black" |
| Hex-строка | "#ff8800", "ff8800", "#f80" |
| Кортеж или список из 3 чисел 0..255 | (255, 136, 0), [255, 136, 0] |
Целое число 0xRRGGBB |
0xFF8800 |
Имена не зависят от регистра. Все цвета в формате RGB (красный, зелёный, синий), как в вебе, а не BGR, как в cv2.
Текущий цвет: w.color
Чтобы не повторять color= в каждом вызове:
w.color = "red"
w.rect(0, 0, 8, 8) # красный
w.text(1, 10, "HI") # тоже красный
w.rect(10, 0, 8, 8, color="blue") # color= перекрывает текущий цвет на один вызов
w.color = "white"
Текущий цвет есть у окна, у каждого слоя и у каждого Image. Кадр из with w.frame() as f начинает с цвета того слоя, на который выводится.
Хелперы для цвета
| Функция | Что делает |
|---|---|
hsv(h, s=1.0, v=1.0) |
Цвет из оттенка h (градусы 0..360, дальше по кругу), насыщенности и яркости (0..1). Возвращает кортеж (r, g, b). Удобно для радуги |
mix(color1, color2, t) |
Смесь двух цветов: t=0 даёт первый, t=1 второй. Принимает любой формат цвета |
from lib import hsv, mix
for x in range(32):
w.rect(x, 0, 1, 8, color=hsv(x * 360 / 32)) # радуга
w.set_pixel(5, 5, color=mix("red", "blue", 0.5)) # фиолетовый
Как три действия работают в цвете
| Действие | Что происходит с цветным пикселем |
|---|---|
set |
Пиксель получает цвет |
clear |
Пиксель становится чёрным (цвет игнорируется) |
xor |
Цвет пикселя побитово смешивается с цветом (XOR по каждому каналу). Дважды тем же цветом возвращает исходный. С белым — инверсия цвета |
Чёрный — это «пусто»
В RGB режиме чёрный пиксель считается пустым. Поэтому:
- Слои: где на верхнем слое чёрный пиксель, виден нижний слой. Нельзя положить непрозрачный чёрный пиксель поверх цветного.
- Картинки: чёрные пиксели вставляемой цветной картинки прозрачные (в режиме
transparent=True). - Матрицы: в RGB-матрице чёрный
(0, 0, 0)значит «этого пикселя в картинке нет».
Если вам нужен именно чёрный фон, рисуйте его на нижнем слое окна (там чёрный и так по умолчанию).
Яркость и цвета ч/б окна
w.brightness = 0.5 # затемнить окно вдвое, в любом режиме
w = PixelWidget(on_color="lime", off_color="#001000") # ч/б окно в стиле «зелёный экран»
w.on_color = "orange" # цвета можно менять на лету
on_color и off_color влияют только на показ: рисование остаётся чёрно-белым, и color= по-прежнему недоступен. Хотите по-настоящему разные цвета у фигур — включайте rgb=True.
6. Рисование: пиксели и матрицы
Методы этого раздела и следующего есть у всех, на чём можно рисовать: у окна w, у слоя, у кадра f из with w.frame() as f и у Image. Везде работают одинаково.
set_pixel, clear_pixel, xor_pixel
Каждый из трёх методов вызывается тремя способами:
| Вызов | Что происходит |
|---|---|
set_pixel(x, y) |
Действие над одним пикселем |
set_pixel(matrix) |
Действие над всеми пикселями, где в матрице не ноль |
set_pixel() |
Действие над всей картинкой |
Действие: set_pixel включает (красит), clear_pixel гасит, xor_pixel переключает.
w.set_pixel(3, 4) # включить пиксель
w.xor_pixel(3, 4) # теперь он погас
w.set_pixel(mask) # включить всё по маске
w.clear_pixel(mask) # погасить всё по маске
w.xor_pixel(mask) # переключить всё по маске
w.set_pixel() # включить все пиксели
w.xor_pixel() # инвертировать всю картинку
w.clear_pixel() # очистить всё
В RGB режиме у set_pixel и xor_pixel есть color=, у clear_pixel нет (гасить можно только в чёрный):
w.set_pixel(3, 4, color="red")
w.set_pixel(mask, color="blue") # маска красится одним цветом
w.set_pixel(color="green") # залить всё зелёным
Пиксель за границей сетки игнорируется (при strict=True: IndexError).
Матрицы
Матрица — массив numpy ровно того же размера, что и сетка. Подойдёт и список списков, и Image того же размера. Бывает двух видов:
| Вид | Форма | Что значит | Где работает |
|---|---|---|---|
| Маска | (height, width) |
не ноль = «этот пиксель» | в обоих режимах |
| Цветная картинка | (height, width, 3) |
цвета пикселей, целые 0..255 | только rgb=True |
import numpy as np
mask = np.zeros((32, 32), dtype=bool)
mask[8:24, 8:24] = True
w.set_pixel(mask) # включили квадрат
w.xor_pixel(np.eye(32)) # переключили диагональ
w.clear_pixel(mask) # погасили квадрат
picture = np.zeros((32, 32, 3), dtype=np.uint8)
picture[:, :16] = (255, 0, 0) # левая половина красная
picture[:, 16:] = (0, 0, 255) # правая синяя
w_rgb.set_pixel(picture) # в RGB окне
Правила:
- Маска. Любое число, не равное нулю, значит «включено» (
True,1,255,0.5...). Ноль иFalse— «выключено». В RGB окне маска красится цветомcolor=(или текущим). - Цветная матрица. Пиксель «есть», если он не чёрный.
set_pixel(picture)копирует цвета таких пикселей,clear_pixel(picture)гасит их,xor_pixel(picture)смешивает цвета. - Действие применяется только к пикселям, которые «есть» в матрице. Остальные не меняются. Поэтому два вызова
set_pixel(a)иset_pixel(b)дают объединение, а не замену. - Матрица не того размера — всегда ошибка (
ValueError), а не тихая обрезка. Так вы сразу заметите перепутанные оси. - Подойдут числа и логический тип в масках, целые 0..255 в цветных матрицах. Строки,
None,NaN, дробные числа в цветных матрицах дают ошибку (см. Ошибки).
replace(matrix, color=None)
Заменяет картинку целиком: пиксели, которые в матрице есть, горят, остальные гаснут. Размер матрицы тоже должен совпадать с сеткой. Удобно, когда вы вычисляете кадр целиком (в RGB режиме можно сразу передать (H, W, 3)).
frame = np.random.rand(32, 32) > 0.5
w.replace(frame)
w_rgb.replace(np.random.randint(0, 256, (32, 32, 3)))
get_pixel(x, y) и чтение
Что сейчас на экране в точке (x, y):
- в чёрно-белом режиме
0или1; - в RGB режиме кортеж
(r, g, b).
w.get_pixel(3, 4) # 1
w_rgb.get_pixel(3, 4) # (255, 0, 0)
Координата за границей сетки здесь всегда IndexError (читать несуществующее нельзя).
Индексация: w[x, y]
Короткая запись для чтения и записи пикселей. Первым идёт x, как во всей библиотеке (не как в numpy, где первая строка).
w[3, 4] = "red" # RGB: покрасить пиксель
w[3, 4] = True # включить (белый)
w[3, 4] = False # погасить
w[0:8, 0:8] = "blue" # закрасить квадрат 8×8 (срезы: [x_от:x_до, y_от:y_до])
w[:, 20] = "green" # закрасить целую строку y = 20
print(w[3, 4]) # прочитать пиксель
region = w[0:8, 0:8] # прочитать область: массив формы (h, w) или (h, w, 3)
- В чёрно-белом режиме присваивать можно
True/Falseили1/0. - В RGB режиме любой формат цвета;
True— белый,False— чёрный (целое число — это0xRRGGBB, поэтому0тоже чёрный). - Индексы — обычные координаты (без отрицательных «с конца»): за границей пиксель игнорируется, как у
set_pixel. Срезы соstepне поддерживаются.
7. Рисование: фигуры, текст, картинки, градиент
Все методы этого раздела принимают mode:
| mode | Что делает | Когда нужен |
|---|---|---|
"set" (по умолчанию) |
Включает пиксели (красит цветом) | Обычное рисование |
"clear" |
Гасит пиксели | Стереть фигуру, вырезать дырку |
"xor" |
Переключает: горевший гаснет, погасший загорается | Курсор, мигание, «негатив» |
В RGB режиме они же принимают color=. Всё, что выходит за границы, обрезается. Любая фигура стирается тем же вызовом с mode="clear".
rect(x, y, w, h, fill=True, mode="set", color=None)
Прямоугольник. (x, y) — левый верхний угол, w и h — ширина и высота в пикселях. fill=False рисует только контур шириной в 1 пиксель. Ширина или высота равна 0 — ничего не рисуется, отрицательные — ValueError.
w.rect(0, 0, 32, 32, fill=False) # рамка по краю экрана
w.rect(10, 10, 8, 8) # закрашенный квадрат
w.rect(10, 10, 8, 8, mode="clear") # стереть его
line(x0, y0, x1, y1, mode="set", color=None)
Прямая линия от точки (x0, y0) до (x1, y1). Работает в любом направлении, концы могут быть за экраном.
circle(cx, cy, r, fill=False, mode="set", color=None)
Окружность с центром (cx, cy) и радиусом r (не меньше 0). fill=True закрашивает круг.
text(x, y, text, mode="set", spacing=1, color=None)
Выводит текст шрифтом 3×5 пикселя. (x, y) — левый верхний угол первого символа. spacing — промежуток между буквами. \n переносит на следующую строку. Возвращает x, где закончился текст.
Поддерживаются цифры, латинские A–Z (строчные рисуются как заглавные), пробел и знаки : . , - + ! ? / % = _ ( ) * '. Всё остальное выводится как ?.
end_x = w.text(0, 20, "OK") # end_x = 8
paste(picture, x=0, y=0, mode="set", transparent=True, color=None)
Вставляет картинку picture в точку (x, y) (её левый верхний угол). Картинка может быть любого размера, даже меньше сетки. Допустимые виды:
Image(в том числе слой), чёрно-белый или цветной,- массив
numpyили список списков: 2D (маска) или 3D(h, w, 3)(цветная, толькоrgb=True), - ASCII-строка (
"#"— включено, всё остальное — выключено).
Как красятся пиксели:
- Маска (ч/б
Image, 2D-массив, ASCII-строка): включённые пиксели красятся цветомcolor=(в RGB окне) или текущим. - Цветная картинка (цветной
Image, 3D-массив): пиксели сохраняют свои цвета,color=игнорируется. - Цветную картинку нельзя вставить в чёрно-белое окно (
ValueError).
Координаты могут быть отрицательными: часть, вылезающая за экран, обрезается.
transparent=True(по умолчанию): затрагиваются только пиксели картинки, которые «есть» (не чёрные). Остальное остаётся как было. Сmode="clear"картинка работает как трафарет, вырезающий дырку.transparent=False: область под картинкой заменяется целиком, нули тоже. Работает только сmode="set".
w.paste(".#.\n###", 5, 5) # прямо ASCII-строкой
w.paste(ship, 13, 24) # готовым Image
w_rgb.paste(".#.\n###", 5, 5, color="cyan") # маска красится в цвет
gradient(x, y, w, h, color1, color2, direction="horizontal", mode="set")
Только rgb=True. Заливает прямоугольник плавным переходом от color1 к color2.
direction="horizontal": цвет меняется слева направо;"vertical": сверху вниз.mode="set"(по умолчанию) закрашивает область градиентом,"xor"смешивает градиент с тем, что там уже есть,"clear"стирает область.
w_rgb.gradient(0, 0, 32, 8, "red", "blue")
w_rgb.gradient(0, 10, 8, 20, "black", "white", direction="vertical")
scroll(dx=0, dy=0, wrap=False)
Сдвигает всю картинку на dx вправо и dy вниз (отрицательные значения — влево и вверх). wrap=False: вылезшее за край пропадает, освободившееся место пустое. wrap=True: вылезшее появляется с противоположной стороны.
Про тип координат
Все координаты, размеры и радиусы должны быть целыми числами. Подойдут int, целые из numpy и «целые» дробные (5.0). Дробное с остатком (1.5) или строка дают TypeError.
8. Кадры и анимация
w.frame(layer="main", keep=False)
Контекст кадра для with или async with. Даёт картинку f (с теми же методами рисования), в которой вы рисуете, а при выходе из блока она целиком заменяет содержимое слоя.
layer: на какой слой выводить (имя или объект слоя). Слой создаётся автоматически, если его нет.keep=False: кадр стартует чистым.keep=True: кадр стартует с копией текущего содержимого слоя, и вы дорисовываете поверх.- Если внутри блока произошла ошибка, кадр отбрасывается, на экране остаётся прошлый.
f.show()показывает кадр сразу, не дожидаясь конца блока.- В RGB режиме кадр начинает с цвета слоя; внутри можно менять
f.color.
with w.frame() as f: # чистый кадр
f.circle(16, 16, 10)
with w.frame(keep=True) as f: # дорисовать к тому, что уже есть
f.clear_pixel(16, 16)
with w.frame("hud") as f: # на слой "hud"
f.text(1, 1, "LV 3")
w.loop(fps=25, layer="main", keep=False, frames=None)
Цикл по кадрам: каждый шаг for даёт чистый кадр f, а при переходе к следующему шагу он показывается. Темп держится сам, time.sleep не нужен.
fps: кадров в секунду (больше 0).frames: сколько кадров, потом цикл заканчивается сам.None: бесконечно, пока окно открыто (или пока вы не сделаетеbreak).
for step, f in enumerate(w.loop(fps=25, frames=25)):
f.rect(step, step, 8, 8)
Кадр, нарисованный перед break, всё равно показывается.
w.play(frames, fps=10, repeat=1, layer="main")
Проигрывает готовый список кадров. Кадры можно задавать ASCII-строками, массивами numpy (в том числе (H, W, 3) в RGB режиме) или Image.
repeat: сколько раз проиграть список.None: бесконечно, пока окно открыто.
w.play(["#.#.\n.#.#", ".#.#\n#.#."], fps=4, repeat=None)
Асинхронный режим
Рисующие методы (set_pixel, rect, text и другие) мгновенные, поэтому в async-коде их можно вызывать как обычно, ничего ждать не нужно. Асинхронные версии есть только у того, что действительно ждёт:
| Синхронно | Асинхронно |
|---|---|
with w.frame() as f: |
async with w.frame() as f: |
for f in w.loop(...) |
async for f in w.aloop(...) |
w.play(...) |
await w.aplay(...) |
w.flush() |
await w.aflush() |
Если выходите из aloop через break, вызовите f.show() на последнем кадре.
9. Слои
Слои накладываются друг на друга в порядке создания (последний сверху). Всё, что вы рисуете прямо на w, лежит в слое "main", он есть всегда.
w.layer(name="main", blend=None)
Возвращает слой с таким именем, создавая его, если нет. На слое работают все методы рисования из разделов 6 и 7.
blend: как слой накладывается на нижние."or"(по умолчанию): пиксели слоя, которые «есть» (не чёрные), ложатся поверх нижних, а там, где слой пуст, виден нижний. В чёрно-белом режиме это объединение."xor": слой переключает пиксели нижних слоёв (в цвете: XOR цветов).
bg = w.layer("bg")
hud = w.layer("hud")
bg.rect(0, 14, 32, 18)
hud.text(1, 1, "SCORE")
hud.clear_pixel() # надпись пропала, фон остался
hud.visible = False # спрятать, не стирая
| У слоя | Описание |
|---|---|
layer.name |
Имя |
layer.visible |
True/False: показать или спрятать |
layer.blend |
"or" или "xor" |
layer.color |
Текущий цвет слоя (RGB) |
w.remove_layer(name)
Удаляет слой. Слой "main" удалить нельзя (ValueError).
w.reset()
Удаляет все слои, кроме "main", очищает его и возвращает ему белый цвет. Окно становится пустым, как после запуска (яркость и цвета ч/б окна не меняются). w.clear_pixel() очищает только слой "main", остальные слои не трогает.
10. Image: картинка в памяти
Image — сетка пикселей с теми же методами рисования, что и у окна, но не связанная с экраном. Нарисовать на ней можно сколько угодно, на экране это появится, только если вы вставите её в окно через paste.
from lib import Image
badge = Image(12, 12)
badge.circle(5, 5, 5, fill=True)
badge.text(2, 3, "A", mode="clear")
w.paste(badge, 10, 4)
Создание
| Способ | Описание |
|---|---|
Image(width, height, rgb=False) |
Пустая картинка. rgb=True — цветная |
Image.from_matrix(matrix) |
Из массива numpy или списка списков. 2D даёт чёрно-белую картинку, 3D (H, W, 3) — цветную; размер берётся из матрицы |
Image.from_ascii(art) |
Чёрно-белая из рисунка символами: #, X, 1, @ — включено, всё остальное (лучше .) — выключено |
Image.from_ascii(art, palette={...}) |
Цветная: словарь «символ → цвет», символы вне словаря пустые |
SHIP = Image.from_ascii("""
..#..
.###.
#####
#.#.#
""")
APPLE = Image.from_ascii("""
..g..
.rrr.
rrrrr
.rrr.
""", palette={"r": "red", "g": "lime"})
Пустые строки в начале и конце и общий отступ слева убираются.
Чтение и отладка
| Что | Описание |
|---|---|
image.width, image.height, image.rgb |
Размер и режим |
image.color |
Текущий цвет рисования (только rgb=True) |
image.array |
Копия содержимого: чёрно-белая даёт 0 и 1 формы (H, W), цветная — (H, W, 3) |
image.get_pixel(x, y), image[x, y] |
Чтение пикселя (0/1 или (r, g, b)) |
print(image), image.to_ascii(on="#", off=".") |
Картинка текстом |
image.save_png(path, scale=10) |
Сохранить в PNG с увеличением |
text_width(text, spacing=1)
Отдельная функция: ширина текста в пикселях (для многострочного — самой длинной строки). Нужна для центрирования и бегущей строки.
from lib import text_width
x = (32 - text_width("OK")) // 2
w.text(x, 13, "OK")
11. Синхронизация, отладка, закрытие
Синхронизация с окном
| Метод | Описание |
|---|---|
w.flush(timeout=1.0) |
Ждёт, пока всё, что вы нарисовали до этого вызова, появится в окне. True — дождались (или окно закрыто), False — не успели за timeout секунд. Нужна, когда скрипт вот-вот завершится, а последний кадр должен успеть показаться |
await w.aflush(timeout=1.0) |
То же без блокировки event loop |
w.wait(timeout=None) |
Блокирует, пока окно не закроют (Esc, крестик, close()). Удобно в конце скрипта |
w.close() |
Закрывает окно. После этого вызовы рисования ничего не делают и не падают |
Закрытое окно заново открыть нельзя: создайте новый PixelWidget.
Отладка
| Метод | Описание |
|---|---|
print(w), w.to_ascii(on="#", off=".") |
Картинка текстом: строка на каждую строку сетки (цветные пиксели выводятся как «включённые») |
w.get_frame() |
Копия того, что сейчас на экране (все видимые слои склеены): чёрно-белая даёт 0 и 1 формы (H, W), RGB — (H, W, 3) |
w.get_pixel(x, y), w[x, y] |
Один пиксель |
w.save_png(path, scale=None) |
Сохранить в PNG ровно то, что видно в окне (с цветами и яркостью). scale: во сколько раз увеличить (по умолчанию ширина окна / ширина сетки) |
w.snapshot() |
Запомнить состояние всех слоёв (содержимое, видимость, режим наложения, цвет) |
w.restore(snapshot) |
Вернуть всё как было при snapshot(); лишние слои удаляются |
saved = w.snapshot()
w.text(1, 13, "WAIT")
time.sleep(2)
w.restore(saved)
12. Ошибки
Библиотека проверяет аргументы сразу и объясняет, что не так. Если ошибка случилась, картинка остаётся как была (частично применённых действий нет).
| Ситуация | Исключение |
|---|---|
Матрица не того размера, не 2D/3D, пустая, с NaN |
ValueError |
| Матрица из строк или объектов; цветная матрица из дробных чисел | TypeError |
Цветная матрица не формы (H, W, 3), значения вне 0..255 |
ValueError |
| Цветная матрица или картинка в чёрно-белом режиме | ValueError |
color=, w.color = ..., gradient() в чёрно-белом режиме |
ValueError |
on_color / off_color вместе с rgb=True |
ValueError |
| Неизвестное имя цвета, плохой hex, канал вне 0..255, кортеж не из 3 чисел | ValueError |
Цвет неподходящего типа (True, объект, дробный канал) |
TypeError |
Одна координата вместо двух: set_pixel(3), set_pixel(None, 3) |
TypeError |
Дробная координата с остатком (1.5), строка вместо числа |
TypeError |
Отрицательные w, h, r, spacing, frames, repeat |
ValueError |
Неизвестный mode, blend, anchor, direction |
ValueError |
text — не строка |
TypeError |
Пиксель за границей при strict=True |
IndexError |
get_pixel / w[x, y] за границей |
IndexError |
Индекс не из двух частей: w[3], срез со step |
TypeError / ValueError |
brightness, s, v, t вне 0..1 |
ValueError |
Удаление слоя "main", пустое имя слоя |
ValueError |
fps не больше 0 |
ValueError |
transparent=False вместе с mode не "set" |
ValueError |
| Поток окна упал (причина печатается в консоль) | RuntimeError при следующем вызове |
Пример:
try:
w.set_pixel(np.ones((10, 10)))
except ValueError as error:
print(error) # matrix shape (10, 10) does not match the grid: expected (32, 32) = (rows, columns)
13. Демо-примеры
Каждый пример можно скопировать в отдельный файл рядом с lib.py и запустить. Примеры 1–10 работают в чёрно-белом режиме, 11–15 показывают цвет.
Демо 1. Рамка, маска и бегущий квадрат (синхронный код)
import time
import numpy as np
from lib import PixelWidget
def main():
w = PixelWidget(title="Sync Widget")
time.sleep(0.5)
w.rect(0, 0, 32, 32, fill=False)
time.sleep(1.0)
w.clear_pixel()
pattern = np.fromfunction(lambda r, c: (r + c) % 4 == 0, (32, 32), dtype=int)
w.set_pixel(pattern)
time.sleep(2.0)
w.clear_pixel(pattern)
time.sleep(1.0)
for pos, f in enumerate(w.loop(fps=25, frames=25)):
f.rect(pos, pos, 8, 8)
w.flush()
time.sleep(2.0)
w.clear_pixel()
w.flush()
if __name__ == "__main__":
main()
Что здесь происходит: рамка рисуется прямо на окне; маска включается через set_pixel и гасится через clear_pixel; квадрат бежит по диагонали 25 кадров, темп держит loop. Квадрат 8×8 на сетке 32×32 помещается при позициях 0..24, поэтому frames=25. В конце flush() гарантирует, что последний кадр успел показаться до выхода из скрипта.
Демо 2. То же в асинхронном коде
import asyncio
from lib import PixelWidget
async def main():
w = PixelWidget(title="Async Widget")
await asyncio.sleep(0.5)
pos = 0
async for f in w.aloop(fps=25, frames=25):
f.rect(pos, pos, 8, 8)
pos += 1
await w.aflush()
await asyncio.sleep(1.0)
w.text(1, 1, "ASYNC")
w.line(0, 8, 31, 8)
await w.aflush()
await asyncio.sleep(2.0)
if __name__ == "__main__":
asyncio.run(main())
Рисующие методы (w.text, w.line) вызываются как обычно, асинхронно ведут себя только aloop и aflush. Блокирующего time.sleep внутри async-кода быть не должно, только asyncio.sleep.
Демо 3. Фигуры и режимы рисования
import time
from lib import PixelWidget
w = PixelWidget(title="Shapes")
with w.frame() as f:
f.rect(1, 1, 30, 30, fill=False)
f.circle(16, 16, 10)
f.line(1, 1, 30, 30)
f.circle(16, 16, 4, fill=True, mode="xor")
w.flush()
time.sleep(2)
w.xor_pixel()
w.flush()
time.sleep(2)
Второй шаг w.xor_pixel() без аргументов инвертирует всю картинку (негатив). Обратите внимание на маленький круг в центре: он нарисован с xor, поэтому в местах, где под ним уже были линии, пиксели погасли.
Демо 4. Матрицы: маски и «Жизнь»
import time
import numpy as np
from lib import PixelWidget
w = PixelWidget(title="Matrices")
rows = np.fromfunction(lambda r, c: r % 2 == 0, (32, 32), dtype=int)
columns = np.fromfunction(lambda r, c: c % 2 == 0, (32, 32), dtype=int)
w.set_pixel(rows)
w.flush()
time.sleep(1)
w.xor_pixel(columns)
w.flush()
time.sleep(1)
w.clear_pixel(rows)
w.flush()
time.sleep(1)
rng = np.random.default_rng(1)
cells = rng.random((32, 32)) > 0.7
for step, f in enumerate(w.loop(fps=10, frames=40)):
neighbors = sum(np.roll(np.roll(cells, dy, 0), dx, 1)
for dy in (-1, 0, 1) for dx in (-1, 0, 1) if (dy, dx) != (0, 0))
cells = (neighbors == 3) | (cells & (neighbors == 2))
f.replace(cells)
Первая часть показывает три действия с масками: set_pixel включает полосы, xor_pixel переключает столбцы, clear_pixel гасит полосы. Вторая часть — игра «Жизнь»: новое состояние считается обычным numpy, а f.replace(cells) выводит его целиком. Матрицы везде имеют форму (32, 32), как и сетка.
Демо 5. Фон и HUD на разных слоях
import time
from lib import PixelWidget
w = PixelWidget(title="Layers")
bg = w.layer("bg")
hud = w.layer("hud")
bg.rect(0, 12, 32, 20)
hud.text(1, 1, "SCORE")
hud.text(1, 7, "00042")
w.flush()
time.sleep(2)
hud.clear_pixel()
w.flush()
time.sleep(2)
w.reset()
w.flush()
После hud.clear_pixel() надпись исчезает, а фон остаётся, потому что они на разных слоях. w.reset() убирает все слои и очищает окно.
Демо 6. Image и бегущая строка
import time
from lib import Image, PixelWidget, text_width
SHIP = Image.from_ascii("""
..#..
.###.
#####
#.#.#
""")
w = PixelWidget(title="Ticker")
ship = w.layer("ship")
ship.paste(SHIP, 13, 24)
message = "HELLO WIDGET "
for step in range(text_width(message) + 32):
with w.frame("ticker") as f:
f.text(32 - step, 4, message)
time.sleep(0.04)
Корабль нарисован один раз на своём слое, и бегущая строка его не стирает. Текст на каждом шаге выезжает слева на один пиксель; части за краем экрана обрезаются сами.
Демо 7. Анимация из готовых кадров
from lib import PixelWidget
w = PixelWidget(title="Frames")
w.play([
"""
#.#.
.#.#
""",
"""
.#.#
#.#.
""",
], fps=4, repeat=8)
play принимает кадры в виде ASCII-строк, массивов numpy или Image. Для бесконечного цикла поставьте repeat=None.
Демо 8. Мигающий курсор через xor
import time
from lib import PixelWidget
w = PixelWidget(title="Cursor")
for _ in range(10):
w.xor_pixel(5, 5)
w.flush()
time.sleep(0.3)
Каждый вызов xor_pixel переключает пиксель: был включён — гаснет, был выключен — загорается.
Демо 9. Отладка: картинка в консоль, PNG и откат
import time
from lib import PixelWidget
w = PixelWidget(title="Debug")
w.circle(16, 16, 8)
w.flush()
print(w)
w.save_png("frame.png")
saved = w.snapshot()
w.clear_pixel()
w.flush()
time.sleep(1)
w.restore(saved)
w.flush()
time.sleep(1)
print(w) показывает, что именно нарисовано, текстом. save_png сохраняет картинку файлом. snapshot и restore откатывают окно к сохранённому состоянию.
Демо 10. Широкое окно в углу экрана
from lib import PixelWidget, text_width
w = PixelWidget(width=64, height=16, window_size=(256, 64), title="Wide")
message = "THE WINDOW STAYS IN THE CORNER "
for step in range(text_width(message) + 64):
with w.frame() as f:
f.text(64 - step, 5, message)
f.rect(0, 0, 64, 16, fill=False)
w.flush()
Окно 256×64 для сетки 64×16 (каждый пиксель в 4 раза крупнее) прижимается правым нижним углом к тому же месту экрана, что и окно по умолчанию. Если поменять window_size, угол останется на месте.
Демо 11. Цвет: color=, текущий цвет, индексация
import time
from lib import PixelWidget, hsv, mix
w = PixelWidget(rgb=True, title="Colors")
w.rect(0, 0, 10, 10, color="red")
w.circle(16, 16, 8, fill=True, color="#00ff88")
w.line(0, 31, 31, 0, color=(255, 128, 0))
w.set_pixel(5, 5, color=mix("red", "blue", 0.5))
w.color = "yellow"
w.text(1, 22, "HI")
w.text(14, 22, "OK", color="cyan")
for x in range(32):
w[x, 31] = hsv(x * 360 / 32)
w[0:3, 12:15] = "white"
print(w.get_pixel(5, 5), w[0, 0], w[20, 31])
w.flush()
time.sleep(4)
Что здесь происходит: фигуры рисуются разными способами указания цвета (имя, hex, кортеж, mix); после w.color = "yellow" цвет действует до следующей смены, а color="cyan" перекрывает его на один вызов; нижняя строка закрашивается радугой через индексацию w[x, y]; print показывает цвета как кортежи (r, g, b).
Демо 12. Радуга и градиенты
import time
from lib import PixelWidget, hsv
w = PixelWidget(rgb=True, title="Rainbow")
w.gradient(0, 0, 32, 8, "red", "blue")
w.gradient(0, 10, 32, 6, "black", "white")
w.gradient(0, 18, 32, 6, "yellow", "magenta", direction="horizontal")
w.flush()
time.sleep(2)
for shift, f in enumerate(w.loop(fps=30, frames=90)):
for x in range(32):
f.rect(x, 0, 1, 32, color=hsv((x * 12 + shift * 6) % 360))
f.circle(16, 16, 6, fill=True, mode="clear")
f.circle(16, 16, 6, color="white")
Первая часть показывает три градиента (по умолчанию слева направо). Вторая — бегущая радуга: на каждом кадре цвет столбца зависит от x и номера кадра, в центре вырезается круг режимом clear и обводится белой окружностью.
Демо 13. Цветной спрайт на градиентном фоне (слои)
import time
from lib import Image, PixelWidget
APPLE = Image.from_ascii("""
..g..
.rrr.
rrrrr
rrrrr
.rrr.
""", palette={"r": "red", "g": "lime"})
w = PixelWidget(rgb=True, title="Sprite")
background = w.layer("background")
background.gradient(0, 0, 32, 32, "navy", "teal", direction="vertical")
sprites = w.layer("sprites")
for step in range(28):
sprites.clear_pixel()
sprites.paste(APPLE, step, 2 + step // 2)
w.flush()
time.sleep(0.05)
hud = w.layer("hud")
hud.text(4, 24, "YUM", color="yellow")
w.flush()
time.sleep(2)
Слои идут в порядке создания: background (фон), sprites (яблоко), hud (надпись). Фон не стирается, когда яблоко движется по своему слою. Чёрные (пустые) пиксели спрайта прозрачны, поэтому сквозь них виден градиент. Надпись лежит на самом верхнем слое. Если бы вы нарисовали её прямо на w (слой main, который создан первым и лежит ниже всех), её закрыл бы непрозрачный градиент.
Демо 14. Чёрно-белое окно с цветом показа и яркостью
import time
from lib import PixelWidget
w = PixelWidget(on_color="lime", off_color="#001000", title="Terminal")
w.rect(0, 0, 32, 32, fill=False)
w.text(3, 4, "READY")
w.text(3, 12, "> OK")
w.flush()
time.sleep(2)
w.on_color = "orange"
w.off_color = "#100800"
w.flush()
time.sleep(2)
for level in range(10, -1, -1):
w.brightness = level / 10
time.sleep(0.15)
Рисуем обычным чёрно-белым способом, а показываем «зелёным терминалом», потом «оранжевым», потом плавно гасим через brightness. color= здесь по-прежнему недоступен: это чёрно-белое окно, меняется только то, как оно показано.
Демо 15. Цветные матрицы: плазма
import numpy as np
from lib import PixelWidget
w = PixelWidget(rgb=True, title="Plasma")
y, x = np.mgrid[0:32, 0:32]
for t, f in enumerate(w.loop(fps=20, frames=120)):
v = np.sin(x / 4 + t / 5) + np.sin(y / 5 - t / 7) + np.sin((x + y) / 6 + t / 9)
red = 127.5 * (1 + np.sin(v))
green = 127.5 * (1 + np.sin(v + 2.1))
blue = 127.5 * (1 + np.sin(v + 4.2))
f.replace(np.stack([red, green, blue], axis=2).astype(np.uint8))
Кадр вычисляется целиком обычным numpy как массив формы (32, 32, 3) с целыми 0..255, а f.replace(...) выводит его в окно. Так же можно показывать любые изображения, подготовленные numpy или OpenCV (не забудьте перевести BGR в RGB: frame[:, :, ::-1]).
14. Частые проблемы и ограничения
| Симптом | Причина и решение |
|---|---|
| Окно исчезает сразу после запуска | Окно живёт, пока работает программа. Добавьте в конец w.wait() или time.sleep(...) |
| Последний кадр не успел показаться | Вызовите w.flush() перед завершением скрипта |
| Окно не там, где ожидалось | Посмотрите w.position (левый верхний угол окна). Если по position нет монитора, используется fallback_position. Проверьте anchor: он определяет, какой угол окна стоит в точке position |
| Окно смещается при смене размера | Так быть не должно: положение считается от угла (anchor). Если у вас своя точка position, убедитесь, что она указывает на нужный угол, а не на левый верхний |
| Картинка мерцает при анимации | Рисуйте через with w.frame() или w.loop(), а не серией отдельных вызовов с очисткой посередине |
ValueError: color= is available only with rgb=True |
Цвет есть только в RGB режиме: создайте окно как PixelWidget(rgb=True). Для «цветного показа» ч/б окна используйте on_color / off_color |
| Нарисованный чёрным цветом пиксель «ничего не делает» | В RGB режиме чёрный значит «пусто». Чтобы стереть, используйте clear_pixel или mode="clear"; чёрный на верхнем слое прозрачен |
| Цвета перепутаны (красный стал синим) | Библиотека использует RGB. Если массив пришёл из OpenCV (BGR), переверните каналы: frame[:, :, ::-1] |
set_pixel(матрица) ничего не стирает |
Так задумано: set_pixel только включает. Для замены картинки целиком используйте replace(matrix), для стирания — clear_pixel(mask) |
ValueError: matrix shape ... does not match the grid |
Форма матрицы должна быть (height, width) (или (height, width, 3) для цвета). Проверьте matrix.shape и, возможно, перепутанные оси |
TypeError: an RGB matrix must contain integers 0..255 |
Цветную матрицу нужно привести к целым: frame.astype(np.uint8) |
w[3, 4] вернул не тот пиксель |
В индексации первым идёт x (столбец), потом y (строка), не как в numpy |
| Фигура «обрезалась» | Так задумано: всё за границами сетки пропускается. Для одиночных пикселей можно включить strict=True |
RuntimeError: the window thread has crashed |
В консоли выше напечатан traceback: там настоящая причина |
Последний кадр перед break в aloop не виден |
Вызовите f.show() перед break |
| Окно не закрывается по Esc | Esc срабатывает, когда окно в фокусе (оно не отбирает фокус само). Кликните по окну или вызовите w.close() |
Ограничения
- В одном процессе одновременно живёт одно окно (
pygameне поддерживает несколько окон сразу). Для нескольких «экранов» используйте слои. Послеclose()можно создать новое окно, в том числе другого режима. - Нет полупрозрачности: пиксель либо есть (не чёрный), либо его нет.
- Режим (
rgb) выбирается при создании окна и не переключается на лету. - Позиционирование по мониторам, режим «поверх всех» и скрытие с панели задач работают только в Windows. На других системах окно просто создаётся в указанной точке.
- Шрифт один (3×5), без масштаба и без кириллицы.
Metadata
Release files for pixel-widget 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| pixel_widget-0.1.0.tar.gz | 47.7 kB | Without provenance | Details for pixel_widget-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| pixel_widget-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for pixel_widget-0.1.0-py3-none-any.whl |
Total release size: 79.3 kB
Release files / pixel_widget-0.1.0.tar.gz
| Download URL | pixel_widget-0.1.0.tar.gz |
|---|---|
| Size | 47.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5af719f77deecaf48d793225412bf5a04d0f8873c6fd2821ec29c2a794350c38
|
|
BLAKE2b-256 checksum How to use checksums |
3bfc3e1f5ef6007d7c6745c869f210c47e334dc3d1f73450b9dd75b57d479724
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.0
|
Release files / pixel_widget-0.1.0-py3-none-any.whl
| Download URL | pixel_widget-0.1.0-py3-none-any.whl |
|---|---|
| Size | 31.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9d548552bb9a2d1066a76c287f5a04d05c2774eda5ae946e0732fd437f7cd8b2
|
|
BLAKE2b-256 checksum How to use checksums |
245879eafe79c8a9f3ac3393a760fa7e9a0c931fdc4ea6d676cf7eba77ffcfab
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.0
|
Security for pixel-widget 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.