Разработчик представил Quite OK Image, алгоритм сжатия без потерь со сложностью O(n)

Разработчик Доминик Саблевски (Dominic Szablewski) представил алгоритм QOI (Quite OK Image), который позволяет без потерь сжимать RGB и RGBA изображения до размера файла, аналогичного для формата PNG, но в 20–50 раз быстрее. Автор отметил у себя в блоге, что алгоритм оказался «до глупости простым». Код проекта доступен на GitHub.

b6706799b809092dbcd3fe74e041fb98.jpg

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

Особенность Quite OK Image в том, что кодирование и декодирование происходит за один проход по изображению — алгоритм касается каждого пикселя только один раз и при этом каждый пиксель кодируется одним из четырех различных способов.

Результирующие значения записываются в чанки, начиная с 2…4 tag-бита, который указывает на способ кодирования, а затем следует количество данных в битах. Все чанки побайтово выровнены. 

Как уже было сказано выше, алгоритм включает в себя 4 разных типа кодирования пикселей:

  • прогон предыдущего пикселя : если текущий пиксель в точности совпадает с предыдущим, то длина серии увеличивается на единицу. В случае обнаружения пикселя, отличающегося от предыдущего, длина серии сохраняется в закодированных данных и текущий пиксель упаковывается одним из трех других методов. Фрагменты длины прогона бывают двух видов и зависят от количества необходимых битов.

QOI_RUN_8 {
    u8 tag  :  3;   // b010
    u8 run  :  5;   // 5-битовый интервал повторения предыдущего пикселя: 1..32
}

QOI_RUN_16 {
    u8 tag  :  3;   // b011
    u16 run : 13;   // 13-битовый интервал повторения предыдущего пикселя: 33..8224
}
  • индексирование ранее просмотренного пикселя: кодировщик хранит массив из 64 ранее просмотренных пикселей и если обнаруживает пиксель, до сих пор хранящийся в массиве, то сохраняет его индекс в поток.

    Для сохранения сложности O (n) в массиве используется только один вид поиска по хешам значения rgba (r^g^b^a). Линейный поиск или другие алгоритмы усложнили бы кодировщик и замедлили бы его.

QOI_INDEX {
    u8 tag  :  2;   // b00
    u8 idx  :  6;   // 6-битный индекс в массиве цветов: 0..63
}
  • отличие от предыдущего пикселя: если цвет текущего пикселя не критично отличается от предыдущего, то разница между этими пикселями записывается в поток. Есть три вида записи, которые выбираются в зависимости от того, насколько велика разница. Примечательно, что обработка альфа-канала занимает больше времени, чем RGB.

QOI_DIFF_8 {
    u8 tag  :  2;   // b10
    u8 dr   :  2;   // 2-битная разница красного канала: -1..2
    u8 dg   :  2;   // 2-битная разница зеленого канала: -1..2
    u8 db   :  2;   // 2-битная разница синего канала: -1..2
}

QOI_DIFF_16 {
    u8 tag  :  3;   // b110
    u8 dr   :  5;   // 5-битная разница красного канала: -15..16
    u8 dg   :  4;   // 4-битная разница зеленого канала:  -7.. 8
    u8 db   :  4;   // 4-битная разница синего канала:  -7.. 8
}

QOI_DIFF_24 {
    u8 tag  :  4;   // b1110
    u8 dr   :  5;   // 5-битная разница красного канала: -15..16
    u8 dg   :  5;   // 5-битная разница зеленого канала: -15..16
    u8 db   :  5;   // 5-битная разница синего канала: -15..16
    u8 da   :  5;   // 5-битная разница альфа-канала: -15..16
}
  • целые значение RGBA: если три предыдущих способов не удается применить, то в поток сохраняются целые значения пикселя.

QOI_COLOR {
    u8 tag  :  4;   // b1111
    u8 has_r:  1;   // красный байт
    u8 has_g:  1;   // зеленый байт
    u8 has_b:  1;   // синий байт
    u8 has_a:  1;   // альфа-байт
    u8 r;           // значение красного байта, если has_r == 1: 0..255
    u8 g;           // значение зеленого байта, если has_g == 1: 0..255
    u8 b;           // значение синего байта, если has_b == 1: 0..255
    u8 a;           // значение альфа-байта, если has_a == 1: 0..255
}

Автор сравнил работу своего алгоритма с libpng и stb_image. Тесты показали, что qoi обгоняет популярные библиотеки по скорости кодирования и декодирования в 20–50 раз, но при этом размер итогового файла получается таким же или незначительно меньше. Также разработчик заявил о том, что планирует применить полученный опыт для создания эффективного видеокодека.

© Habrahabr.ru