mod: ядро (14) циферки в виде фигур
This commit is contained in:
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user