Клиент продаёт физические наборы для стринг-арт — картин, которые выкладываются нитью по гвоздикам на круглой или квадратной раме. Наборы продаются заранее, а схему сборки под конкретное лицо/фото покупатель получает уже после оплаты.
Нужно было сделать веб-сервис, где покупатель по коду из набора загружает своё фото, а сервис превращает его в готовую пошаговую инструкцию: последовательность номеров гвоздей, на которые наматывается нить. Требования: несколько вариантов обработки фото на выбор, автоматическое избавление от фона у портретов, поддержка и круглой, и квадратной рамы с разным числом гвоздей и плотностью нити под конкретный купленный набор, и удобный плеер для сборки без интернета "по памяти" — с озвучкой, автовоспроизведением и сохранением прогресса.
Собрал систему из четырёх частей.
Backend на Laravel (Lumen): хранит купленные коды и их параметры (форма, число гвоздей, плотность нити), админку цепочек фильтров и логику выдачи/погашения кода.
Админ-панель на React: администратор без программиста собирает произвольные цепочки фильтров (яркость, контраст, гамма, постеризация, маска резкости, "чернильный" эффект по краям, замена фона) отдельно для круглых и квадратных наборов — на выходе по каждой активной цепочке для одного фото генерируется свой вариант картины.
Отдельный Node.js-сервис применяет эти фильтры: часть — через sharp, часть (резкость, обводка) — через собственные WebGL-шейдеры, отрисованные в headless-режиме без браузера.
Для чистого выделения портрета на фото подключил отдельный Python-сервис с нейросетью MODNet (портретный matting через ONNX Runtime) — по одному запросу отдаёт PNG с прозрачным фоном.
Ядро — сам алгоритм стринг-арт: взял открытую реализацию (image-stylization-threading) и переписал её вокруг React-компонента, вынеся весь расчёт в Web Worker с OffscreenCanvas, чтобы несколько вариантов картины считались параллельно, не подвешивая интерфейс. Поскольку исходная библиотека была рассчитана на конкретную HTML-страницу с слайдерами, а не на переиспользование, пришлось написать слой заглушек, эмулирующий её внутреннее API, чтобы запустить расчёт "headless" внутри воркера.
Передачу фото и результатов зашифровал гибридной схемой RSA+AES с собственной чистой JS-реализацией — сервис должен был работать и без HTTPS, где встроенное браузерное шифрование недоступно.
Плеер сборки: канвас с анимацией текущей и уже проложенных нитей, подсветка активного гвоздя, автовоспроизведение с выбором скорости (расчёт оставшегося времени сборки), голосовая озвучка шагов, фоновая музыка и сохранение прогресса на сервере — можно закрыть вкладку и продолжить с того же места хоть с другого устройства по своей уникальной ссылке.
Рабочий сквозной путь от загрузки фото до готовой инструкции: пользователь вводит код, кадрирует фото, получает несколько сгенерированных вариантов картины на выбор за 15–25 секунд (проверено на схемах вплоть до 4000 шагов и 300 гвоздей), выбирает понравившийся и получает по почте персональную ссылку на плеер сборки.
Один и тот же движок обслуживает и круглые, и квадратные рамы с разным числом гвоздей (240/300/360) под разные линейки наборов — новую цепочку обработки фото под очередной набор администратор добавляет сам, через интерфейс, без правок кода.