← На главную

Chrome портит маленькие JPEG — переходите на SVG

12.08.2026 14:00 · hackernews

Один логотип, отрисованный в 15px, выглядел на экране коллеги тоньше и ближе к оригиналу, чем на своём. В Firefox картинка казалась толще, в Chrome — тоньше. Замена на SVG решала проблему, но оставался вопрос: почему Chrome вообще так рисует JPEG? Разобрались — и нашли аккуратную оптимизацию.

Интуитивный способ показать маленькую картинку из JPEG — полностью разжать файл в памяти и потом уменьшить. Это расточительно. Если у тебя JPEG 2000×2000, а показать нужно 20×20, полный растровый кадр занимает примерно 12 MB, а итоговая картинка — всего 1.2 KB. Почти всё из большого изображения при масштабировании выбрасывается.

И вот ключевая мысль: теряется не случайная информация. При сильном уменьшении исчезают в основном высокочастотные детали. Дерево с кучей листьев после сжатия до 20×10 превращается в зелёное пятно и коричневую палку. Высокочастотная мелочь смешивается и почти не остаётся.

Дальше нужно вспомнить, как устроен JPEG. При сжатии картинка делится на блоки 8×8, и каждый блок переводится в частотную область — это DCT, дискретное косинусное преобразование. В блоке есть набор базовых функций: от плоского цвета вверху слева до шахматной доски внизу справа. Количество каждой такой составляющей в блоке — это коэффициенты. Дальше JPEG эти коэффициенты сжимает, но для данной истории это не важно.

Теперь главное. Если уменьшаешь картинку в 8 раз, каждый блок 8×8 становится одним пикселем. Для такого мелкого результата нужны в основном низкие частоты — высокие всё равно пропадут. Поэтому можно не разжимать JPEG целиком, а пропустить высокочастотные коэффициенты и взять только то, что нужно для грубой версии. Такой фокус называется partial IDCT scaling — обратное преобразование только части коэффициентов. Он работает и для других дробей со знаменателем 8. Почитать про него можно на jpegclub.org.

Chrome делегирует декодирование и отрисовку картинок библиотеке Skia. Для JPEG она использует libjpeg-turbo, которая умеет partial IDCT scaling. Когда целевой размер маленький, Chrome не разжимает картинку целиком, а выбирает ближайшую дробь со знаменателем 8 и декодирует в этом масштабе, а потом уже доводит до нужного размера обычным алгоритмом. Вот почему логотип выглядел толще: при таком мелком рендере оставалась только постоянная составляющая, без смягчения краёв и градиентов.

Мораль простая: не используй JPEG для иконок. Формат и его оптимизации заточены под восприятие фотографий. Он так и называется: Joint Photographic Experts Group.

Читать оригинал →