import sys from PIL import Image def encode_to_base36(num): """Кодирует число в base-36: 0-9, A-Z, a-z""" if num < 0: raise ValueError("Number must be non-negative") charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" if num < len(charset): return charset[num] result = [] while num > 0: result.append(charset[num % len(charset)]) num //= len(charset) return ''.join(reversed(result)) def generate_halftone_svg(image_path, output_path, cols=80, max_radius=8, animation_variants=12): import random from collections import defaultdict img = Image.open(image_path).convert('L') aspect_ratio = img.height / img.width rows = int(cols * aspect_ratio) img_resized = img.resize((cols, rows), Image.Resampling.LANCZOS) step = max_radius * 2 + 2 width = cols * step height = rows * step # Генерируем CSS-классы для разных задержек animation_classes = [] for i in range(animation_variants): delay = (i / animation_variants) * 3 encoded_i = encode_to_base36(i) # Убрать .0 для целых чисел: 1.0s → 1s if delay == int(delay): delay_str = f"{int(delay)}" else: delay_str = f"{delay:.1f}".lstrip('0') animation_classes.append(f".{encoded_i}{{--d:{delay_str}s}}") # Собираем элементы группируя по классам: {class: [(radius, cx, cy), ...]} elements_by_class = defaultdict(list) unique_radii = set() for y in range(rows): for x in range(cols): brightness = img_resized.getpixel((x, y)) factor = (255 - brightness) / 255.0 if factor < 0.15: continue radius = int(factor * max_radius) if radius == 0: continue cx = x * step + step // 2 cy = y * step + step // 2 anim_class = random.randint(0, animation_variants - 1) encoded_class = encode_to_base36(anim_class) elements_by_class[encoded_class].append((radius, cx, cy)) unique_radii.add(radius) # Создаем с определениями кружков для каждого радиуса defs = '' for radius in sorted(unique_radii): encoded_radius = encode_to_base36(radius) defs += f'' defs += '' # Создаем группы по классам groups = [] for anim_class in sorted(elements_by_class.keys()): group_elements = elements_by_class[anim_class] uses = ''.join( f'' for radius, cx, cy in group_elements ) if uses: groups.append(f'{uses}') animation_css = "".join(animation_classes) groups_html = "".join(groups) svg_template = f'''{defs}{groups_html}''' with open(output_path, 'w') as f: f.write(svg_template) print(f"SVG успешно сгенерирован: {output_path}") if __name__ == '__main__': file_name_in = sys.argv[-1] generate_halftone_svg(file_name_in, f'{file_name_in.split(".")[0]}j.svg', cols=63, max_radius=7)