Кодирование изображений
Изображение превращается в хеш тремя функциями из hazehash/encode. Они принимают RGBA-пиксели sRGB с прямым (не premultiplied) альфа-каналом, поэтому ImageData из canvas подходит как есть. Кодируйте на сервере или при сборке и отправляйте клиенту только строку: энкодер это большая половина библиотеки (около 6,3 КБ gzip), и браузеру, который только показывает плейсхолдеры, он не нужен.
import { encode, encodeToString, toBase64Url } from 'hazehash/encode'Вход это обычный объект, одной формы во всех функциях:
interface RgbaImage {
data: Uint8Array | Uint8ClampedArray // RGBA, длина 4 · width · height
width: number
height: number
}Кодирование в строку
encodeToString(image, options?)
string
Кодирует изображение и возвращает хеш в base64url без выравнивания, готовый для текстовой колонки или JSON. Это функция, которая нужна в большинстве случаев.
import { encodeToString } from 'hazehash/encode'
const hash = encodeToString({ data: rgba, width: 1280, height: 959 })
// "Ed7UwRWKKv5znm6a7sC1tziHDNpMuCikxrYpIg"image
RgbaImage
Пиксели для кодирования. В data должно быть ровно 4 · width · height байт, а width и height должны быть целыми числами не меньше 1; иначе вызов бросает PlaceholderError с кодом InvalidInput. Функция описывает одну картинку, поэтому для анимированного изображения передайте один кадр.
options
EncodeOptions · необязательно
См. Опции энкодера ниже.
Кодирование в байты
encode(image, options?)
Uint8Array
То же, что encodeToString(), но возвращает сырые байты. Используйте для бинарной колонки (BYTEA, BLOB), которая примерно на 25% меньше текстовой формы; см. Хранение хешей.
import { encode } from 'hazehash/encode'
const bytes = encode({ data: rgba, width: 1280, height: 959 })
bytes.length // 28Преобразование между двумя формами
toBase64Url(bytes)
string
Превращает байты хеша в строку base64url. Обратная функция, toBytes(), экспортируется декодером; см. Декодирование хешей.
import { encode, toBase64Url } from 'hazehash/encode'
toBase64Url(encode({ data: rgba, width, height })) // та же строка, что вернёт encodeToString()Опции энкодера
budget
number · по умолчанию: 28
Максимальный размер результата в байтах, вместе с заголовком. Это потолок, а не фиксированный размер: простое изображение, например однотонное или с плавным градиентом, занимает меньше байт, а хеш, который уже помещается, никогда не дополняется. Наименьшее рабочее значение 7 байт, или 9, если в хеше есть альфа; всё, что меньше, бросает BudgetTooSmall.
Бенчмарк охватывает бюджеты от 16 до 48 байт. См. Выбор бюджета.
profile
'fast' | 'default' | 'high' · по умолчанию: 'default'
Насколько усердно энкодер ищет решение. 'fast' перебирает меньше сеток и занимает около 1 мс для входа 64×48, 'default' перебирает все и занимает около 5 мс (изображения с альфа-каналом медленнее, около 100 мс), а 'high' дополнительно подгоняет коэффициенты под изображение для анализа, давая примерно на 1% меньшую ошибку, и занимает около 14 мс. Время зависит от размера входа только через уменьшение. Любое другое значение бросает InvalidInput.
alpha
'auto' | boolean · по умолчанию: 'auto'
Хранит ли хеш прозрачность. 'auto' добавляет блок альфа-канала, только если есть пиксель с альфой ниже 254/255, true добавляет его всегда, а false никогда, и тогда прозрачное изображение кодируется так, как будто оно непрозрачное.
analysisSize
number · по умолчанию: 64
Длинная сторона в пикселях сетки, до которой изображение уменьшается перед анализом. Значения округляются и ограничиваются диапазоном 32–128. Более крупная сетка редко помогает, ведь хеш в любом случае очень грубое описание.
weights
{ L?: number; C?: number; A?: number } · по умолчанию: { L: 1, C: 1, A: 1 }
Насколько каждый канал учитывается в ошибке, которую минимизирует энкодер: L для яркости, C для двух цветовых каналов и A для альфы. Больший вес тратит на этот канал больше бюджета. Вес должен быть конечным числом не меньше 0, иначе вызов бросает InvalidInput.
Пример — все опции сразу:
const hash = encodeToString(
{ data: rgba, width: 1280, height: 959 },
{
budget: 24,
profile: 'high',
alpha: false,
analysisSize: 96,
weights: { L: 1, C: 1.5, A: 1 },
},
)Выбор бюджета
Хеш из n байт занимает ceil(4n / 3) символов base64url. Средняя ΔE это перцептивная ошибка в OKLab (умноженная на 100), усреднённая по 513 изображениям; чем меньше, тем лучше.
| Бюджет | Символов | Средняя ΔE | Комментарий |
|---|---|---|---|
| 16 | 22 | 8.41 | самый компактный практичный хеш |
| 20 | 27 | 7.89 | всё ещё на 14% лучше ThumbHash |
| 24 | 32 | 7.54 | хороший компромисс размера и качества |
| 28 | 38 | 7.29 | по умолчанию |
| 36 | 48 | 7.00 | больше деталей, когда размер не важен |
Переход с 28 на 24 байта стоит около 3,4% ошибки за 14% экономии данных, а с 28 на 36 байт даёт 4,0% выигрыша за 29% дополнительных данных. Берите 24 байта, когда важнее хранилище, и 28 в остальных случаях. Методика описана в разделе Бенчмарк.
Заметки
- Ядро считает входные данные sRGB и игнорирует ICC-профили и EXIF-ориентацию. Хелперы для Node.js и командная строка переводят в sRGB и применяют ориентацию через
sharp. - Результат детерминирован: одни и те же пиксели и опции всегда дают одни и те же байты.
- Кодирование не предназначено для главного потока браузера при каждом рендере. Посчитайте хеш один раз заранее и сохраните его.