mod: ядро (14) циферки в виде фигур

This commit is contained in:
2026-09-06 00:55:22 +03:00
parent 217056b37b
commit 5904fc675d
4 changed files with 53 additions and 19 deletions
+42 -15
View File
@@ -3,7 +3,7 @@ import math
import random
import re
from collections import defaultdict
from typing import BinaryIO, Union
from typing import BinaryIO, Optional, Union
from PIL import Image
@@ -24,7 +24,9 @@ def encode_to_base36(num: int) -> str:
return "".join(reversed(result))
def generate_shape_def(shape: str, radius: int, shape_id: str) -> str:
def generate_shape_def(
shape: str, radius: int, shape_id: str, max_radius: int = 8, char: Optional[str] = None
) -> str:
"""Генерирует SVG-элемент для тега <defs> в зависимости от типа фигуры."""
r = radius
match shape:
@@ -88,6 +90,18 @@ def generate_shape_def(shape: str, radius: int, shape_id: str) -> str:
pts_str = " ".join(points)
return f'<polygon id="{shape_id}" points="{pts_str}"/>'
case "binary":
if char is None:
# Вероятностная функция: вероятность "0" пропорциональна оптической плотности
# В тенях преобладают '0' (~85%), но с шансом ~15% выскакивают '1'
# В светах преобладают '1' (~85%), но с шансом ~15% выскакивают '0'
prob_zero = min(0.85, max(0.15, (r / max_radius) if max_radius else 0.5))
char = "0" if random.random() < prob_zero else "1"
font_size = max(4, round(r * 2.0))
return (
f'<text id="{shape_id}" font-family="ui-monospace,monospace" font-weight="900" font-size="{font_size}">{char}</text>'
)
case "heart":
r_top = round(r * 0.3)
r_mid = round(r * 0.4)
@@ -194,7 +208,7 @@ def generate_halftone_svg(
# 2. Обход пикселей и группировка
elements_by_class = defaultdict(list)
unique_radii = set()
unique_shape_keys = set()
is_animated = blink > 0
for y in range(grid_height):
@@ -216,14 +230,24 @@ def generate_halftone_svg(
anim_index = rng.randint(0, animation_variants - 1) if is_animated else 0
encoded_class = encode_to_base36(anim_index)
elements_by_class[encoded_class].append((radius, cx, cy, anim_index))
unique_radii.add(radius)
if shape == "binary":
# Вероятность выпадения '0' плавно растет с оптической плотностью
# В тенях преобладают '0' (~85%), но с шансом ~15% выскакивают '1'
# В светах преобладают '1' (~85%), но с шансом ~15% выскакивают '0'
prob_zero = 0.15 + 0.70 * (radius / max_radius if max_radius else factor)
char = "0" if rng.random() < prob_zero else "1"
shape_id = f"s{encode_to_base36(radius)}{char}"
unique_shape_keys.add((radius, char, shape_id))
else:
shape_id = f"s{encode_to_base36(radius)}"
unique_shape_keys.add((radius, None, shape_id))
elements_by_class[encoded_class].append((shape_id, cx, cy, anim_index))
# 3. Формирование <defs>
defs_list = ["<defs>"]
for radius in sorted(unique_radii):
shape_id = f"s{encode_to_base36(radius)}"
defs_list.append(generate_shape_def(shape, radius, shape_id))
for radius, char, shape_id in sorted(unique_shape_keys, key=lambda k: (k[0], k[1] or "")):
defs_list.append(generate_shape_def(shape, radius, shape_id, max_radius=max_radius, char=char))
defs_list.append("</defs>")
defs_html = "".join(defs_list)
@@ -254,8 +278,8 @@ def generate_halftone_svg(
animation_classes.append(f".a{encoded_class}{{--d:{delay_str}s}}")
uses = "".join(
f'<use href="#s{encode_to_base36(radius)}" x="{cx}" y="{cy}"/>'
for radius, cx, cy, _ in group_elements
f'<use href="#{shape_id}" x="{cx}" y="{cy}"/>'
for shape_id, cx, cy, _ in group_elements
)
if uses:
groups.append(f'<g class="a{encoded_class}">{uses}</g>')
@@ -299,9 +323,9 @@ def generate_halftone_svg(
svg_css = (
f"<style>"
f"svg{{background:transparent}}"
f"circle,rect,polygon,path{{{fill_style}transform-origin:center;{anim_rule}transition:all .5s ease-out}}"
f"circle,rect,polygon,path,text{{{fill_style}transform-origin:center;{anim_rule}transition:all .5s ease-out}}"
f"{keyframes_rule}"
f".frozen circle,.frozen rect,.frozen polygon,.frozen path{{animation:none!important;opacity:{opacity:.1f}!important;transform:none!important}}"
f".frozen circle,.frozen rect,.frozen polygon,.frozen path,.frozen text{{animation:none!important;opacity:{opacity:.1f}!important;transform:none!important}}"
f"{animation_css}"
f"</style>"
)
@@ -340,7 +364,7 @@ def prepare_gallery_svg(svg_content: str) -> str:
hover_css = (
"svg{--hypn0-play:paused}"
":host(:hover) svg,svg:hover{--hypn0-play:running!important}"
"circle,rect,polygon,path{animation-play-state:var(--hypn0-play,paused)!important}"
"circle,rect,polygon,path,text{animation-play-state:var(--hypn0-play,paused)!important}"
)
if "</style>" in svg_content:
@@ -364,8 +388,10 @@ def prepare_active_svg(svg_content: str) -> str:
gallery_css_variants = [
"svg{--hypn0-play:paused}:host(:hover) svg,svg:hover{--hypn0-play:running!important}.shape,circle,rect,polygon,path{animation-play-state:var(--hypn0-play,paused)!important}",
"svg{--hypn0-play:paused}:host(:hover) svg,svg:hover{--hypn0-play:running!important}circle,rect,polygon,path{animation-play-state:var(--hypn0-play,paused)!important}",
"svg{--hypn0-play:paused}:host(:hover) svg,svg:hover{--hypn0-play:running!important}circle,rect,polygon,path,text{animation-play-state:var(--hypn0-play,paused)!important}",
"svg{--hypn0-play:paused}svg:hover{--hypn0-play:running}.shape,circle,rect,polygon,path{animation-play-state:var(--hypn0-play,paused)!important}",
"svg{--hypn0-play:paused}svg:hover{--hypn0-play:running}circle,rect,polygon,path{animation-play-state:var(--hypn0-play,paused)!important}",
"svg{--hypn0-play:paused}svg:hover{--hypn0-play:running}circle,rect,polygon,path,text{animation-play-state:var(--hypn0-play,paused)!important}",
]
cleaned = svg_content
for gcss in gallery_css_variants:
@@ -374,7 +400,7 @@ def prepare_active_svg(svg_content: str) -> str:
cleaned = re.sub(r"svg\s*\{[^}]*--hypn0-play:\s*paused[^}]*\}", "", cleaned)
cleaned = re.sub(r"(:host\(:hover\)\s*svg\s*,\s*)?svg:hover\s*\{[^}]*--hypn0-play:[^}]*\}", "", cleaned)
cleaned = re.sub(
r"(\.shape,)?circle,rect,polygon,path\s*\{animation-play-state:\s*var\(--hypn0-play,\s*paused\)!important\}",
r"(\.shape,)?circle,rect,polygon,path(,text)?\s*\{animation-play-state:\s*var\(--hypn0-play,\s*paused\)!important\}",
"",
cleaned,
)
@@ -423,7 +449,8 @@ def analyze_svg_structure(svg_content: str) -> dict:
rect_count = len(re.findall(r"<rect\b", svg_content))
polygon_count = len(re.findall(r"<polygon\b", svg_content))
path_count = len(re.findall(r"<path\b", svg_content))
defs_count = circle_count + rect_count + polygon_count + path_count
text_count = len(re.findall(r"<text\b", svg_content))
defs_count = circle_count + rect_count + polygon_count + path_count + text_count
# Группы и классы
groups_count = len(re.findall(r"<g\b", svg_content))