Главная / Свои и фото-пазлы
Свои и фото-пазлы

Image slide puzzle — как работает нарезка

Техническая сторона image slide puzzle: как изображение становится N×N плитками, почему приложения не режут файл на самом деле, и какие рендеринг-трюки держат анимацию плавной.

Обновлено 2026-05-20 6 мин чтения

Эта статья про инженерную сторону image slide puzzle: как произвольное фото становится играбельным N×N пазлом. Это часть, которую игроки никогда не видят, а разработчики тратят на неё больше времени, чем ожидают.

Конвейер

Три стадии, по порядку:

1. Квадратный crop

Большинство фото не квадратные. HEIC-файлы iPhone — 4:3; фото, снятые в портретной — 3:4. Доска слайд-пазла — 1:1. Шаг один — выбрать, какой квадратный регион фото играть.

Два распространённых подхода:

Центрированный crop. Берёт самый большой центрированный квадрат, помещающийся внутри. Быстро, предсказуемо, иногда неправильно (субъект не в центре).

Интерактивный crop. Показывает пользователю перетаскиваемый квадрат. Пусть выбирает. Медленнее, но пользователь всегда получает что хотел.

Slide Puzzle использует интерактивный crop, по умолчанию центрированный. Crop неразрушающий — оригинал в библиотеке фото никогда не меняется.

2. Уменьшение до рабочего разрешения

После кропа изображение обычно всё ещё 3000×3000 или больше (зависит от камеры). Для игры в пазл это перебор. Доска 6×6, показываемая в 320pt на 3× экране iPhone, рендерит каждую плитку в около 53pt = 160 пикселей устройства. Исходное разрешение 1024×1024 даёт примерно 170 пикселей на плитку — резче, чем экран может показать.

Уменьшение до 1024 (иногда 2048) — стандарт. Большие источники тратят память и замедляют загрузку без улучшения видимого результата.

3. Рендер плиток по требованию

Это часть, которую большинство ошибочно реализует с первой попытки. Изображение на самом деле не режется на 16 отдельных файлов. Это было бы медленно, расточительно и не нужно.

Вместо этого приложение рендерит каждую плитку, отрисовывая одно и то же исходное изображение в прямоугольник размером с плитку, со смещением исходного прямоугольника на позицию плитки в целевом изображении. CSS называет это трюком «background-position»; iOS — это клиппинг прямоугольника CGImage; SwiftUI использует Rectangle().clipped() поверх Image.

В псевдокоде, отрисовать плитку в целевой позиции (row, col) на N×N доске с изображением стороны S:

draw image at (-col * S/N, -row * S/N)
within a clip rectangle of (S/N × S/N)

Это всё. Шестнадцать плиток для 4×4 — это 16 вызовов рендера того же изображения с 16 разными смещениями. Приложение никогда не режет файл.

И поэтому нарезка доски 6×6 мгновенна: это 36 вызовов того же рендерера, а не 36 файловых операций.

А как же анимация

Сдвиг плитки — это transform-перевод отрисованной плитки. Содержимое внутри плитки не меняется — только позиция кадра-клиппинга на экране. Анимация сдвига становится тривиальной для GPU и идёт в 120 Гц на дисплеях ProMotion.

Три детали реализации, которые имеют значение:

Хранение

Где реально живёт импортированное изображение?

В уважающем приватность приложении — в песочнице приложения, зашифрованной iOS в покое. Библиотека фото по-прежнему хранит оригинал; приложение хранит рабочую копию 1024×1024 внутри своей папки Documents. Когда пользователь удаляет приложение, рабочая копия удаляется с ним.

В облачном приложении рабочая копия живёт где-то на сервере. Последствия очень разные — для приватности, для оффлайн-игры и для того, что случится, когда сервер исчезнет. Slide Puzzle — это песочница-вариант.

Бюджет памяти

Типичный 4×4 image slide puzzle:

Нормально. На 6×6 источник и буфер декода того же размера. Доске больше памяти не нужно.

Где бюджеты памяти ужесточаются — это в библиотеках обложек: 300 обложек × 4 МБ = 1,2 ГБ, если декодировать все сразу. Приложения избегают этого через декод по требованию (только когда обложка показана) и освобождение при бэкграунде (в памяти только изображение активной игры).

Граничные случаи

Три вещи, которые ломаются на практике:

Фото с тегом ориентации EXIF. Фото, снятое в портретной, но сохранённое в ландшафтной с тегом поворота, отобразится правильно в библиотеке фото и неправильно в пазле, если приложение забыло применить EXIF-поворот. Первая версия каждого фото-пазл-приложения имеет этот баг.

Очень большие исходные изображения. Некоторые HEIC-файлы — 6000×8000 пикселей. Загрузка в память в полном разрешении упадёт на меньших iPhone. Лечение — потоковое декодирование с уменьшением до целевого размера: Apple ImageIO поддерживает. Декодируйте в 2048×2048 сразу с диска; никогда не декодируйте в полном размере.

Субпиксельный рендеринг. Размеры плиток, не делящиеся точно на пиксельную сетку экрана, создают дробный пиксельный столбец на одной стороне каждой плитки. С чистым клиппингом это видно как волосок-зазор. Лечите либо привязкой размеров плиток к целым пикселям (видимый дрожь на нестандартных размерах), либо позволяя плиткам перекрывать на 0,5 пикселя (нет зазора, нет дрожи).

Это не глубокие проблемы, но их единодушно забывают первые реализаторы.

Сводка

Конвейер: crop → resize → клип-и-перевод для рендера плиток → translate transforms для анимации сдвигов. В приложении, уважающем приватность, изображение никогда не покидает устройство. Весь конвейер укладывается в около 200 строк кода, плюс ещё 50 на обработку EXIF-поворота, которую все забывают в первый раз.

Image slide puzzles проще, чем кажутся снаружи. Изображение остаётся одним изображением; плитки — это просто оформленные виды на него.