Сегодня отправить картинку – плевое дело. Мы обмениваемся фото по несколько раз, а то и десятков раз в день. Производители смартфонов хвастаются камерами, а самое интересное тут то, что все это было бы невозможно без JPEG-сжатия. Этот хитрый алгоритм может эффективно сжимать огромные фото, чтобы ими можно было обмениваться, но как он это делает?
На этот вопрос ответит Игорь Позняев, автор канала «Блог системного администратора». Почему «шакалы шакалятся», как фотография размером 35 Мб сжимается в сотни, а то и в тысячи раз. Попробую объяснить так, чтобы понял каждый!
Итак, у нас есть картинка
Надо понимать, что любая картинка в своем «истинном» виде – огромна. Так, например, фото размером 12 мегапикселей в своем «сыром» виде весит почти 35 мегабайт – такой размер не слишком подходит для того, чтобы постоянно отправлять своим друзьям фотки котиков, а как быстро закончится память на телефоне – вообще страх.
Так происходит потому, что буквально каждый пиксель на фото содержит информацию. Даже если все пиксели черные – они будут закодированы по отдельности. Теперь представьте, что цвет пикселя – не самостоятельный. Это – смесь красного, синего и зеленого (RGB), и каждому из них нужно присвоить какое-то значение. Получается, что на один пиксель фотографии необходимо 24 бита, или три байта. Умножаем это на разрешение, и получаем, что жалкие 40-50 фотографий могут занять один гигабайт памяти на телефоне.
Но они не занимают. Все дело в сжатии, которое позволяет уменьшить размер фотографии, практически не потеряв в качестве. Как? Наше зрение неидеально, и мы можем его как бы «обхитрить». Глаз не видит огромного числа деталей – и именно на этом основан алгоритм сжатия JPEG.
Как это работает?
Первый этап сжатия – субсэмплинг, или, проще – прореживание. Суть в том, чтобы «разобрать» изображение, а потом заново его «собрать», только уже без повторяющейся информации, ведь цвет можно передать не только при помощи RGB. Так, изображение разбивают на Y, Cb, Cr – звучит страшно, но не пугайтесь, первый канал – это яркость, а два других – это цветовые каналы. Вычисляются они по довольно страшным формулам.
Нужно это для того, чтобы после можно было без проблем объединять пиксели в изображениях.
Немного для понимания: наши глаза устроены так, что цвета мы различаем гораздо хуже, чем яркость. Малейшее изменение яркости глаз заметит, а вот изменение цвета – нет. Поэтому если мы просто объединим пиксели в палитре RGB, то есть усредним и цвета, и яркость, фото будет выглядеть «пикселизированным». Но если мы усредним только цвета соседних пикселей, оставив их яркость в покое (таким образом, чтобы пиксели с одинаковым цветом имели разную яркость), мы сможем сэкономить место, ведь для кодирования цветов 4 пикселей нам надо будет знать цвет только одного.
И если раньше нам надо было тратить полный байт на кодирование одного цвета, теперь мы тратим тот же байт на кодирование цветов сразу 4 пикселей. То есть 6 байт вместо 12. Уже на этом этапе мы уменьшаем размер фото вдвое.
Дальше самое интересное – сжатие
Как ни странно, картинки – не уникальны. Любое монохромное изображение можно представить, используя всего 64 картинки, вот они:
Разделив изображение на блоки по 8 пикселей, мы обнаружим, что они идеально совпадают с паттернами выше. Теперь самое интересное: из этих блоков составляется матрица 8 на 8 значений, где каждая ячейка – это как бы паттерн тех блоков, которые вы видите выше, в том же порядке. Каждой ячейке присваивается значение, как часто тот или иной паттерн используется в конечном изображении.
Теперь обратим внимание на то, что слева сверху находятся менее детализированные «рисунки», а справа снизу – более детализированные. Первые отвечают за монотонные, «сплошные» участки изображения, а вторые – за детали. Вы ведь наверняка помните фото с шакалами:
А теперь посмотрите на последнее фото. Узоры ничего не напоминают? Именно! Мы можем достаточно четко разглядеть те самые паттерны, которыми это фото сжато. Но это – финальный результат, до которого осталась последняя стадия.
Квантование
Достаточно сложный процесс, но я попробую объяснить простыми словами. Итак, у нас имеется уже готовая матрица, в которой находятся как паттерны, так и степень их участия в формировании готовой картинки. Теперь, чтобы картинку непосредственно сжать, алгоритм проходится по всей этой матрице зигзагом, деля значения из матрицы на коэффициент, и округляя их до ближайшего целого числа.
Вот она – магия! После квантования отметается огромное количество мельчайших деталей, которые наш глаз заметить не может. Буквально на глазах «огромная» картинка сжимается до приемлемого размера. Конечно, в какой-то степени страдает и качество, но, во-первых, при приемлемом сжатии оно страдает не сильно, а во-вторых, это позволяет уменьшить размер файла буквально в десятки раз.
После этого у нас получается уже не картинка, а последовательность чисел, где каждое число кодирует как определенный паттерн, так и частоту его появления. Это число сокращается по алгоритму Хаффмана, и вот в таком виде передается к вам на компьютер. И вот так картинка, которая весила бы 40 мегабайт, без сильной потери качества будет весить всего пару мегабайт.
Ну а для того, чтобы из последовательности чисел снова получить картинку, достаточно просто проделать эти шаги в обратном порядке. Все достаточно просто!
Конечно, мы пользуемся не только JPEG. Этот стандарт распространен, однако он довольно старый, и уже сейчас у него все больше альтернатив, начиная от HEIC, заканчивая PNG (кстати, тоже немолодой формат). Однако JPEG эффективен, к нему все привыкли, и думаю, он с нами еще надолго.
Именно этому формату стоит сказать спасибо за тот интернет, который мы знаем сейчас. Пускай он иногда и «шакалит» картинки.