File size: 5,815 Bytes
53aafe9 51b769e 53aafe9 51b769e 53aafe9 51b769e 53aafe9 51b769e 53aafe9 51b769e 2b2ad8f 6f8f93d 51b769e 53aafe9 51b769e a35ccd1 53aafe9 51b769e 53aafe9 6f8f93d 51b769e 2b2ad8f 51b769e 53aafe9 6f8f93d 53aafe9 2b2ad8f 51b769e 53aafe9 51b769e 1883218 51b769e 53aafe9 09c24d3 51b769e 6f8f93d 51b769e 1883218 51b769e 53aafe9 09c24d3 53aafe9 a35ccd1 53aafe9 a35ccd1 51b769e 53aafe9 6f8f93d 53aafe9 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | 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'<svg[^>]*>', '', raw_data, flags=re.I)
inner = re.sub(r'</svg>', '', inner, flags=re.I)
inner = re.sub(r'<\?xml[^>]*\?>|<!DOCTYPE[^>]*>', '', inner, flags=re.I)
if 'fill=' not in inner:
inner = f'<g fill="black">{inner}</g>'
return inner, vbox
except:
return '<rect width="100" height="100" fill="black"/>', "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 = ('<rect width="100" height="100" fill="black"/>', "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'<g transform="{transform}">{symbol_content}</g>')
return f'<svg xmlns="http://www.w3.org/2000/svg" width="{canvas_w}" height="{canvas_h}" viewBox="0 0 {canvas_w} {canvas_h}" style="background:white;">{"".join(body)}</svg>'
except Exception as e:
return f'<svg width="800" height="200"><text y="50" fill="red" font-size="20">Error: {str(e)}</text></svg>', 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=7860) |