Делаем тетрис в QR-коде, который работает
Не просто тетрис, но и совершенно рабочий QR-код. Потестите!
Впервые такой QR-тетрис я увидел на канале linkmeup. Приложенный QR-код не читался, и авторы не удостоили его комментарием. Решил, что это повод разобраться в основах самому и предложить улучшенный вариант — не просто веселую, но и функциональную пикчу.
Что в исходном коде?
Кадр из исходного сообщения
За время пандемии количество упоминаний QR-кодов в интернете кратно выросло, но даже это не мешает найти доступную информацию по внутреннему устройству QR-кодов. На Хабре есть статья «Читаем QR-код» от tgx, которой уже 11 лет. В самом начале говорится о служебных областях кода, которые необходимы для корректного определения кода: «маркеры» в трех углах и «тайминги» — чередование пикселей между маркерами.
Области детектирования. Источник
Обратите внимание, что «тайминг» сверху отсутствует, а слева полностью нарушен. Кроме того, код не квадратный, его размер 39×40. Возможно, это была сложная загадка, но я быстро отвлекся: хотелось генерировать подобные анимации с условием, что код читается всегда.
Сперва небольшое теоретическое введение.
Теория
Начнем с определения термина QR-код.
QR-код (англ. Quick Response code — код быстрого отклика; сокр. QR code) — тип матричных штриховых кодов (или двухмерных штриховых кодов), изначально разработанных для автомобильной промышленности Японии. Его создателем считается Масахиро Хара. Сам термин является зарегистрированным товарным знаком японской компании «Denso». Источник: Википедия
При создании QR-кода используется избыточное кодирование. Это значит, что повреждение кода или некорректное считывание позволит декодировать информацию верно. Есть четыре уровня избыточности: 7, 15, 25 и 30 процентов. Большая избыточность позволяет наносить в центр кода картинку.
QR-коды разных версий
Местоположение «ошибок» не имеет значения, поэтому для тетриса удобнее всего «снять» блоки между двумя маркерами. В зависимости от количества закодированной информации QR-код изменяет свой размер и дополняется промежуточными маркерами. Размер кода определяется его версией. Версия 1 описывает самый маленький код с размером 21×21 пиксель, а версия 40 — самый большой на 177×177 пикселей.
Вырез в QR-коде
Размер QR-кода зависит от размера входных данных и настроенной избыточности, поэтому стоит прибегнуть к математике. Самая очевидная функция — гипербола вида y=ax^2+bx+c. Начало координат я поместил в верхний левый угол кода. Для нахождения коэффициентов a, b и с нужны три точки, я выбрал следующие:
- верхний правый угол левого маркера,
- верхний левый угол правого маркера,
- точка на вертикальной линии, делящей QR-код пополам.
Третья точка подбирается опытным путем. На всякий случай я решил использовать максимальную избыточность и взял примерно треть от высоты кода. Такое «повреждение» нереалистично с точки зрения тетриса из-за пустых мест под маркерами, зато позволяет генерировать интересные структуры, которые можно заполнить фигурами из тетриса.
Посмотрим, как это можно реализовать.
Практика
Для реализации задумки я использую свой рабочий язык программирования — Python. Для генерации QR-кодов существует библиотека qrcode. Она позволяет получить доступ к сгенерированному QR-коду как к двумерному массиву булевых значений, где каждый элемент соответствует пикселю.
Более того, библиотека умеет как сохранять в файл, так и выводить на stdout, что облегчает предпросмотр результата.
import qrcode
qr = qrcode.QRCode(
error_correction=qrcode.constants.ERROR_CORRECT_H
)
qr.add_data("Du hast”, optimize=True)
qr.make()
qr.modules_count # Размер кода в пикселях
qr.modules # Двумерный массив пикселей
qr.print_ascii() # Вывод в терминал
qr.make_image().save("qr.png”) # Сохранение в изображение
Создание, редактирование и отображение QR-кода реализовано. Нетрудно заметить, что qrcode умеет создавать только статические изображения. Для формирования анимированных изображений я использовал библиотеку imageio.
filenames = ["1.png", "2.png", "3.png"]
duration = [0.5, 0.5, 0,1]
with imageio.get_writer("output.gif", mode='I', duration=duration) as writer:
for filename in filenames:
image = imageio.imread(filename)
writer.append_data(image)
Примера из документации достаточно для создания анимированного изображения за одним исключением. В ней предлагается фиксированная частота смены кадров. Но если вместо одного числа передать массив, то каждое число будет соответствовать времени демонстрации кадра.
Третьей библиотекой стала numpy — для решения системы уравнений. Избыточно, но работает.
Для оптимизации, конечно, стоило решить систему уравнений один раз и записать коэффициенты. Можно даже самостоятельно реализовать метод решения матричных уравнений. Но нет. Это же прототип.
Не буду описывать проверку коллизий на поле тетриса, там решение наивное. Вместо этого расскажу, как пользоваться инструментом.
Инструкция по эксплуатации
На текущий момент «потрогать» утилиту можно только самым «хардкорным» способом: клонированием с репозитория на github. Процесс установки:
git clone https://github.com/Firemoon777/qrtetris.git
cd qrtetris
python3 -m pip install -r requirements.txt
Запуск производится следующим образом:
python3 -m qrtertis -d "Тест!" -o output.gif
По умолчанию используется одна последовательность действий из одной фигуры, но есть возможность задать собственную последовательность через параметр -p. Он принимает аргумент в виде строки-программы, где каждая инструкция разделена точкой с запятой. «Программу» можно записать в файл, по одной инструкции на строку, и передать через тот же аргумент, поставив символ @ перед путем до файла.
Поддерживаются следующие инструкции:
- spawn N — создать фигуру в центре вверху. Возможные аргументы:
— SQUARE
— T
— Z
— Z_REVERSED
— I - left — сдвинуть фигуру на один пиксель влево,
- right — сдвинуть фигуру на один пиксель вправо,
- down — сдвинуть фигуру на один пиксель вниз,
- drop — быстро двигать фигуру вниз до тех пор, пока она не займет место,
- rotate [1×2|3] — повернуть на 90, 180 или 270 градусов по часовой стрелке.
Обращаю внимание, что для всех команд, кроме drop, проверка коллизий не производится. Также нет «физики», которая тянет фигурку вниз. Без силы гравитации можно настроить сколь угодно быстрое движение фигурок. Интервал между выполнением команд можно назначить через аргумент -i.
Заключение
Всего лишь один пост без комментариев побудил меня сделать небольшую утилиту, которая совмещает QR-коды и тетрис.
Еще один вариант
Полезно ли это? Вполне. Довольно веселый и интерактивный способ позвать кого-нибудь на ивент или попромить страничку. В QR-код выше, например, зашита ссылка на актуальные вакансии Selectel.
Было ли это весело и познавательно для меня? Определенно.
На данный момент утилита достаточно «сырая» и не лишена изъянов. Оставляйте свои замечания или даже предлагайте исправления.
Ссылка на репозиторий →
Что еще интересного я делал на Хабре:→ Написал бота, которые делает стикеры из сообщений в Telegram
→ Собрал радио из Cyberpunk 2022
→ Сделал ПХМ-1 из лаборатории Доктора Дью