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'', '', 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''
except Exception as e:
return f'', 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=7860)