import math import re from flask import Flask, render_template, request from PIL import Image, ImageOps app = Flask(__name__) def prepare_svg_symbol(file_storage): try: raw_data = file_storage.read().decode('utf-8') vb_match = re.search(r'viewBox=["\']([^"\']+)["\']', raw_data, re.I) vbox = vb_match.group(1) if vb_match else "0 0 100 100" inner = re.sub(r']*>', '', raw_data, flags=re.I) inner = re.sub(r'', '', inner, flags=re.I) inner = re.sub(r'<\?xml[^>]*\?>|]*>', '', inner, flags=re.I) if 'fill=' not in inner: inner = f'{inner}' return inner, vbox except: return '', "0 0 100 100" @app.route('/') def index(): return render_template('index.html') @app.route('/generate', methods=['POST']) def generate(): try: # --- Считываем параметры --- density = int(request.form.get('density', 40)) gap_x = float(request.form.get('gap_x', 0)) gap_y = float(request.form.get('gap_y', 0)) shift_stagger = float(request.form.get('shift_stagger', 0)) base_scale = float(request.form.get('base_scale', 1.0)) growth = float(request.form.get('growth', 0)) growth_type = request.form.get('growth_type', 'sine') invert_mode = request.form.get('invert') == 'true' # --- Параметры холста --- canvas_w = 1400 canvas_h = 900 step = canvas_w / density rows = math.ceil(canvas_h / step) # --- Подготовка шейпа --- svg_file = request.files.get('shape_svg') if svg_file and svg_file.filename != '': symbol_content, v_box = prepare_svg_symbol(svg_file) else: symbol_content, v_box = ('', "0 0 100 100") if request.form.get('current_svg_symbol'): symbol_content = request.form.get('current_svg_symbol') v_box = request.form.get('current_vbox', "0 0 100 100") vb_parts = [float(x) for x in v_box.split()] vb_w, vb_h = vb_parts[2], vb_parts[3] # --- Обработка Растрового Изображения (МАСКА) --- raster_file = request.files.get('raster_image') pixel_map = None if raster_file and raster_file.filename != '': try: img = Image.open(raster_file) img = ImageOps.fit(img, (canvas_w, canvas_h), method=Image.Resampling.LANCZOS) img = ImageOps.grayscale(img) pixel_map = img.load() except: pass # --- Генерация SVG --- body = [] cell_w_max = step * (1.0 - gap_x) cell_h_max = step * (1.0 - gap_y) for r in range(rows): row_shift = (r * shift_stagger * step) for c in range(-density, density * 2): grid_x = c * step + row_shift grid_y = r * step if grid_x < -step or grid_x > canvas_w: continue # 1. ПРОВЕРКА ПО КАРТИНКЕ (ВИДИМОСТЬ) if pixel_map: sample_x = max(0, min(int(grid_x + step/2), canvas_w - 1)) sample_y = max(0, min(int(grid_y + step/2), canvas_h - 1)) brightness = pixel_map[sample_x, sample_y] / 255.0 # Порог видимости: если яркость выше 0.5 — считаем это светом is_visible = brightness > 0.5 if invert_mode else brightness < 0.5 if not is_visible: continue # 2. ВЫЧИСЛЕНИЕ ШИРИНЫ ПО МАТЕМАТИЧЕСКОМУ АЛГОРИТМУ wave_norm = 0 if growth_type == 'sine': # Диагональная волна wave_norm = (math.sin(c * 0.3 + r * 0.3) + 1) / 2 elif growth_type == 'linear': # Слева направо wave_norm = (c % density) / density elif growth_type == 'steps': # Ступеньки wave_norm = (r % 5) / 5.0 # Модификатор ширины: чем больше growth, тем сильнее сужается объект # При growth = 0 ширина всегда 1.0 (максимум) mod_w = 1.0 - (wave_norm * growth) mod_w = max(0.01, min(1.0, mod_w)) # Итоговые размеры content_w = cell_w_max * base_scale * mod_w content_h = cell_h_max * base_scale cx = grid_x + step / 2 cy = grid_y + step / 2 scale_x = content_w / vb_w scale_y = content_h / vb_h transform = f'translate({cx:.2f} {cy:.2f}) scale({scale_x:.4f} {scale_y:.4f}) translate({-vb_w/2:.2f} {-vb_h/2:.2f})' body.append(f'{symbol_content}') return f'{"".join(body)}' except Exception as e: return f'Error: {str(e)}', 200 if __name__ == '__main__': app.run(host='0.0.0.0', port=7860)