Формат хеша
Как устроен HazeHash, для тех, кто хочет разобраться в хеше или написать другой декодер. Это формат версии 1 в реализации hazehash 0.1. Соответствие определяют зафиксированные тестовые векторы в репозитории (packages/core/test/vectors/v1.json): декодер должен выдавать тот же RGBA с точностью ±1 на канал.
Обзор
Изображение превращается в короткую строку байт: фиксированный заголовок, за которым следуют коэффициенты DCT изображения в OKLab, закодированные по Голомбу–Райсу. Бюджет по умолчанию 28 байт (38 символов base64url), поддерживаемый диапазон 16–48 байт. Один заголовок занимает 7 байт, или 9 с альфой, а хеш не может быть длиннее 1024 байт.
Биты записываются старшим вперёд, а многобитные поля идут в big-endian. Хвост последнего байта дополняется нулями. Бит за концом данных читается как 0, поэтому обрезанная строка допустима и декодируется с меньшей детализацией.
Заголовок
Заголовок занимает 56 бит:
| Поле | Бит | Смещение | Значение |
|---|---|---|---|
| ver | 2 | 0 | 0 для версии 1; 1–3 зарезервированы и должны отклоняться |
| alpha | 1 | 2 | 1, если после заголовка идёт блок альфа-канала |
| aspect | 6 | 3 | r = 2^((code − 32) / 8), код 32 это 1:1 |
| Lx − 1 | 3 | 9 | ширина сетки яркости, 1–8 |
| Ly − 1 | 3 | 12 | высота сетки яркости, 1–8 |
| Cx − 1 | 2 | 15 | ширина сетки цвета, 1–4 |
| Cy − 1 | 2 | 17 | высота сетки цвета, 1–4 |
| DC L | 6 | 19 | L = q / 63 |
| DC a, DC b | 6+6 | 25, 31 | v = q · 0.64 / 63 − 0.32 |
| scale L/a/b | 4×3 | 37 | код предельной амплитуды AC для каждого канала |
| k L/a/b | 2×3 | 49 | параметр Райса для каждого канала |
| reserved | 1 | 55 | записывается как 0, при чтении игнорируется |
Блок альфа-канала занимает 16 бит и присутствует только при alpha = 1: DC A (5 бит, A = q / 31), Ax − 1 (2), Ay − 1 (2), scale A (4), k A (2) и зарезервированный бит (1).
Читатель должен отклонять вход короче заголовка (InvalidLength), длиннее 1024 байт и неизвестную версию (UnsupportedVersion).
Соотношение сторон и размер вывода
Пропорции хранятся как code = clamp(round(8 · log2(W / H)) + 32, 0, 63) с округлением половин от нуля, так что один шаг это около 9%, а диапазон примерно от 1:16 до 15:1. Декодер, которого просят превью с длинной стороной size, выдаёт size × max(1, round(size / r)) при r ≥ 1 и max(1, round(size · r)) × size в остальных случаях. Соотношение нужно для рисования плейсхолдера; для вёрстки страницы берите настоящие размеры изображения.
Коэффициенты
Изображение раскладывается в базисе DCT-II с выборкой в центрах пикселей на единичном квадрате:
f(u, v) = Σ c[i][j] · cos(π i u) · cos(π j v)c[0][0] это среднее значение канала, оно лежит в заголовке. Остальные коэффициенты сетки nx × ny хранятся, только когда 2 · (i · ny + j · nx) < 3 · nx · ny, то есть i / nx + j / ny < 1.5, что отрезает только дальний угол сетки. Число хранимых AC-коэффициентов сетки n × n равно 0, 3, 8, 14, 23, 32, 45 и 57 для n = 1…8.
Порядок в потоке
Все AC-коэффициенты всех каналов образуют одну последовательность, отсортированную по:
ρ² = (i² · ny² + j² · nx²) / (nx² · ny²), сравнивается перекрёстным умножением в целых числах,- затем по каналу в порядке L, a, b, A,
- затем по меньшему
j, затем по меньшемуi.
Поэтому отрезание хвоста убирает самые высокие частоты всех каналов сразу.
Квантование
У каждого канала есть масштаб s = smax · 2^(−(15 − code) / 3), где smax = 0.64 для L и A и 0.32 для a и b. Коэффициент кодируется целым числом q из [−Qmax, Qmax]:
q = round(sign(c) · sqrt(min(|c| / s, 1)) · Qmax)
ĉ = sign(q) · s · (|q| / Qmax)²Qmax равно 7 для яркости и 3 для a, b и альфы на любой частоте. Декодер обязан восстанавливать ровно по этой формуле, без смещения восстановления, и ограничивать декодированное q значениями ±Qmax.
Энтропийное кодирование
Каждое q отображается в n = 2q (при q ≥ 0) или −2q − 1 (при q < 0) и записывается как n >> k единичных бит, нулевой бит, затем k младших бит n. k (0–3) хранится для каждого канала. Нулевой коэффициент стоит k + 1 нулевых бит, поэтому энкодер может отбросить хвостовые нулевые байты данных, из-за чего хеш простого изображения короче бюджета.
Декодирование в пиксели
- Восстановить каждый канал разделимым синтезом в размере вывода.
- Ограничить L диапазоном
[0, 1]и перевести OKLab в линейный RGB матрицами Оттоссона. - Отображение в гамму: если какая-либо компонента выходит за
[−0.0005, 1.0005], масштабировать цветность на наибольшееt ∈ [0, 1], найденное 8 шагами деления пополам, затем ограничить значения диапазоном[0, 1]. - Перевести в sRGB и применить дизеринг interleaved-gradient-noise:
u = fract(52.9829189 · fract(0.06711056 x + 0.00583715 y))иout = floor(v · 255 + u). - Альфа, если есть, это
clamp(A, 0, 1) · 255с округлением. Цвета не умножаются на альфу заранее.
Строковая форма
Строковая форма это base64url (RFC 4648 §5) без выравнивания, так что 28 байт дают 38 символов. Длина, дающая остаток 1 по модулю 4, или любой символ вне алфавита недопустимы.