Генератор QR-кода с нуля — и ошибка, которую видно только сканером
h han@hanproject.ru
->назад к журналу
$cat ~/log/generator-qr-koda-s-nulya

Генератор QR-кода с нуля — и ошибка, которую видно только сканером

Понадобился генератор QR без чужих библиотек. Разбираю, из чего он состоит, где я ошибся так, что код выглядел правильным и не читался, и как проверить такое честно.

На сайте запрещены чужие скрипты — политика безопасности пускает ровно один внешний адрес, счётчик. Значит, для страницы с QR-кодом нужен свой генератор. Тащить библиотеку ради одной страницы всё равно не хотелось: лишний вес и лишнее доверие.

Оказалось, это отличная задача на вечер — и отличный урок про то, как проверять результат.

Из чего состоит QR-код

Внутри квадратика четыре слоя, и каждый нужен.

Данные. Текст переводится в байты, к ним приписывается указание режима и длина. Дальше добивка до полного размера — чередующиеся байты, чтобы не оставлять пустоту.

Восстановление. К данным считается код Рида — Соломона: избыточность, которая позволяет прочитать код, даже если часть залита, помята или закрыта пальцем. Считается он не в обычной арифметике, а в поле Галуа из 256 элементов, где сложение — это исключающее ИЛИ, а умножение идёт через таблицы логарифмов. Звучит страшно, пишется в двадцать строк.

Раскладка. Байты укладываются в квадрат змейкой снизу вверх, парами столбцов, справа налево, обходя служебные области: три больших квадрата по углам, синхродорожки, метки выравнивания.

Маска. Готовое поле накладывается на одну из восьми решёток — чтобы не получилось больших однотонных пятен или узоров, похожих на угловые квадраты. Все восемь вариантов считаются, каждому начисляется штраф по четырём правилам, побеждает наименьший.

Ошибка, которую не видно глазами

Когда всё было написано, я сравнил свой результат с эталонным кодировщиком. Версия совпала, размер совпал — а модули разошлись.

Причина оказалась в самом скучном месте. Сведения о формате — какой уровень восстановления и какая маска — записываются в код дважды: одной полосой вокруг левого верхнего угла и второй, разрезанной, снизу слева и справа сверху. Порядок бит в этих двух копиях разный. Я перепутал их местами: писал первую копию туда, где должна лежать вторая, и наоборот.

Самое неприятное в такой ошибке — код выглядит абсолютно правильным. Чёрно-белый квадрат, углы на месте, всё симметрично. Просто не читается.

Как проверить честно

Сравнение с эталоном показало расхождение, но после исправления формата осталась разница в добивке нулями — и тут я задумался, кто из нас прав.

Правильный ответ дал не спор, а проверка результата: в браузере есть встроенный распознаватель штрихкодов. Значит, можно нарисовать свой код на холсте и тут же прочитать его обратно.

Прогнал 59 случайных строк — русских, латинских, со знаками, разной длины — на всех четырёх уровнях восстановления, вплоть до двадцатой версии. Все 59 прочитались ровно тем текстом, что вводился. Отдельно проверил строку Wi-Fi с точкой с запятой внутри пароля: её надо экранировать, иначе телефон поймёт пароль неправильно.

Разница в добивке, из-за которой я переживал, на читаемость не влияет вовсе: распознаватель берёт длину из заголовка и остаток просто не смотрит.

Вывод, который стоил вечера: сравнивать с эталоном полезно, но доказывает совпадение, а не правильность. Правильность доказывает только проверка результата тем, для кого он предназначен.

Мелочь про белые поля

Ещё одно, что часто ломает напечатанные коды: рамка. Считывателю нужна светлая полоса шириной примерно в четыре клетки вокруг кода, иначе он не находит его границы. Дизайнеры её регулярно обрезают «чтобы аккуратнее», и код перестаёт читаться на половине телефонов.

И про подмену адреса: многие генераторы зашивают в код не ваш адрес, а свой — чтобы считать переходы. Когда их сервис закроется, все напечатанные коды умрут. Здесь внутри ровно то, что вы ввели, потому что сервера в этой странице нет вообще.

темы записи инструменты разбор