Повидло из плохих яблок: как, зачем и почему



Хозяйке на заметку: повидло
Для варки яблочного повидла подойдут те фрукты, которые не пригодны для еды – переспевшие,
поврежденные и т.д. – короче говоря, все мягкие или жёсткие, какими вы располагаете.
chudo-povar.com


Категорическая вам конничива, товарищи!
Как и было обещано, принесли вам многабуквенный отчёт про тонкости приготовления повидла из плохих яблок в условиях крайнего юга.

DISCLAIMER: За сочинения у меня в школе была стабильная тройка, поэтому пристегните ремни.
Будет много воды и лабуды. Возможны полный бред, попирание принципов, нарушение законов физики и розовые пони.


Преамбула
Всё началось с того, что одним хмурым октябрьским вечером в беседе с n1k-o речь зашла о нашумевшем несколько лет назад анимационном shadow-art видео «Bad Apple!!».
Если вы в своё время благополучно пропустили вспышку (как, собственно, это сделал я), то узнать, в чём суть, можно здесь.
Вот, чтобы далеко не ходить, то самое видео:



Так вот: n1k-o совершенно справедливо заметил, что конверсии этого видео есть чуть более, чем на всех известных платформах, но вот беда — исключая нашу с вами любимую. Обидно, не так ли? Я тогда отшутился, дескать, раз надо, то я прямо сейчас готов исполнить всё вот это, правда, в спектрумовских атрибутах 32x20.

Шутки шутками, но яблочное зёрнышко засело в голове и потихоньку пускало корни.
Как стало понятно позже, и у Олега тоже.

<nq> но вселенская справедливость должна быть восстановлена
— nq торжественно скрывается на пару часов

Я осознал и проникся.
Не менее хмурой и не менее октябрьской ночью, удостоверившись, что домочадцы мирно пускают пузыри в кроватках, я вколотил первые строки в Makefile. Пути назад не было.

Ограбление по-питоновски
Итак, исходное видео скачано. Посмотрим-ка на то, что мы тут имеем:
VIDEO:  [H264]  960x720  24bpp  30.000 fps  901.3 kbps (110.0 kbyte/s)

Прикидываем наши скромные возможности и решаем ограничиться десятью кадрами в секунду.
Это снизит размер, позволит выводить чётко кадр через четыре и всё ещё будет выглядеть вполне смотрибельно.
Отсюда получаем порядка 2200 кадров, что в спектрумовских монохромных скринах составляет около 13 метров.
Время расчехлять Python, чесать лысину и притаптывать это безобразие!

Раскладывать на диске адовый пасьянс, выдёргивая и сохраняя кадры по одному, не хочется, да и не нужно.
Делаем так:

import subprocess

WIDTH,HEIGHT,FPS = 256,192,10
command = [
    FFMPEG_BIN,
    '-i', SRC_VIDEO,
    '-f', 'image2pipe',
    '-vf', 'scale=%d:%d' % (WIDTH,HEIGHT),
    '-r', '%0.1f' % FPS,
    '-an',
    '-pix_fmt', 'rgb24',
    '-vcodec', 'rawvideo',
    '-v', '0',
    '-'
]

pipe = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=10**8)

После этого мы легко можем брать следующий кадр в формате RGB24 прямо из видео таким образом:
def get_next_frame(pipe):
    raw_frame = pipe.stdout.read(WIDTH*HEIGHT*3) # RGB24
    return raw_frame


Итак, у нас есть RGB24-кадр из видео, взятый с нужным FPS, любезно отмасштабированный ffmpeg'ом до наших 256x192 и преобразованный нехитрым питоновским кодом в 6144 байта монохромного битмапа. Что дальше?

Притаптываем
Первое, что приходит в голову при виде чёрно-белой картинки — это, конечно же, применить всем известное RLE.
Но априори в чистом виде этим тут совершенно не прославиться. Поэтому пока откладываем в сторону и следом берём второе, что приходит в голову: работать не с кадрами, а с дельтой — разницей между двумя последовательными кадрами. С этим уже можно что-то делать.
Дальше, памятуя о том, что всё это ещё надо будет как-то успевать распаковывать, отказываемся от мысли оперировать отдельными битами изображения и переходим к полным байтам, а вернее — к блокам по 8 пикселей в ширину и 1 в высоту.
Набрасываем некоторое количество кода и видим вот что:



Это исходный кадр RGB24, его конверсия в 1bpp, разница этого кадра с предыдущим и, собственно, кучка блоков 8x1, которую надо наложить на предыдущий кадр для получения текущего.

Если немного помедитировать, можно заметить, что наши блоки из-за их размера и содержания видео (все вот эти вот силуэты) чаще всего образуют цепочки по вертикали. Исходя из этого делаем предположение, что оптимальнее RLE будет работать именно со столбцами (идём по битмапу сверху вниз), нежели построчно (слева направо).

Прикидываем на пальцах формат хранения одного кадра. Если опустить ненужные подробности, получается примерно так:
(skip), N,
(literals), %bbbbbbbb, %cccccccc,
(rle), M, %dddddddd,
...,
(end-of-frame)

В общем, всё очевидно, ничего нового. В скобках — условные команды распаковщику:
skip — пропустить N строк экрана, literals — отрисовать следующие за командой блоки, rle — отрисовать следующий блок M раз, и, наконец, end-of-frame — закончили упражнение.

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

Таджу Каге Буншин но Дзюцу!
Понятное дело, что из-за невеликой производительности нашего Z80 нам недоступны алгоритмы сжатия с хитрой математикой, которые в хвост и в гриву гоняют на PC. Однако есть ниндзюцу со сложным и страшным названием и простой, как полено, сутью: VQ, то бишь векторное квантование.



Оставим рассказы про понижение размерности многомерного векторного пространства путём отображения на конечный набор значений из дискретного подпространства меньшей размерности математикам и Википедии, опишем принцип просто и грубо.

Хозяйке на заметку: алгоритмы Алгоритма (sic!)
Будет преступлением не сказать о том, что где-то в этом месте индеец Зоркий Глаз таки узрел на Поуете совершенно замечательный Bad Apple C64 от algorithm'а. Так вот, алгоритмы Алгоритма строятся примерно на той же базе, но куда более изощрённы технически.
За счёт этого видео ужато до ничтожных размеров флопика C64, однако на результат без боли в глазах смотреть трудновато.
Даже с учётом преимуществ Коммодора — возможности стримить с флопа и быстро рисовать.

  • Разбиваем наш кадр сеткой на блоки 8xN пикселей.
  • Каждому уникальному блоку ставим в соответствие свой индекс.
  • Содержимое блока 8xN (N байт) записываем в некую структуру (условно говоря, массив), из которой его можно будет потом получить по индексу (условно говоря, указателю).

Здесь должен был быть рисунок, наглядно демонстрирующий всё это безобразие, но мне очень не хочется его рисовать. Поэтому просто давайте условимся, что он здесь есть. Мы же с вами взрослые люди, да?

Такую структуру хитрые компрессологи называют поваренной кодовой книгой, codebook.
Не правда ли, сразу видна аналогия c картами тайлов и с палитровыми картинками, к примеру, 256c? Там точно таким же образом 8-битному индексу цвета сопоставляется его 24-битное RGB-значение из палитры (по сути, той же кодовой книги).
Дальше, наверное, можно уже и не продолжать, но… терпите уж :)

Исходный кадр теперь можно сохранить в виде набора индексов-указателей на блоки в кодовой книге и собственно самой кодовой книги. При этом 8xN бит блока заменяется M битами его индекса.
При условии, что блоки изображения не всегда уникальны и M << 8xN, мы уже можем заметить некоторую выгоду от этой затеи. Вспомните пример с картинкой 256c, где M=8, 8*N=24: 24 бита заменяется на восемь, и за вычетом размера палитры наша картинка сдувается втрое.
Понятно, что если мы попробуем таким образом перекрутить наше видео целиком, то всё быстро превратится в тыкву: из-за разнообразия блоков книжка распухнет до непотребных размеров, а вместе с ней, соответственно, вырастет и битовая длина M указателя, сводя на нет все преимущества. Вы ещё здесь?

А теперь финт ушами.

Что нам мешает жёстко зафиксировать размер кодовой книги, СРАЗУ забить её наиболее часто встречающимися в видео блоками и при упаковке подбирать для каждого блока индекс НАИБОЛЕЕ ПОХОЖЕГО на него из имеющихся в книге?

Да, собственно, ничегошеньки. Берём и делаем!

Потери потерь
В качестве меры похожести, не мудрствуя лукаво, выбираем что-то типа так называемой дистанции Хэмминга:



С негодованием отвергая буржуазную математическую символику, говорим, что мы ксорим наши блоки побитово и считаем количество единичек. Нормируя по размеру блока в битах, получаем клёвое число в диапазоне от 0.0 (полное совпадение) до 1.0 (вообще нифига не похоже). Профит!

Хозяйке на заметку: Вороной-Делоне и прочие K-средние центроиды
Да, есть куда более красивые и продвинутые варианты всего этого дела, но нам хватит и этого, спасибочки.

Повидло будем делать за два прохода.
На первом мы определяем наиболее часто встречающиеся блоки, формируем из них нашу кодокнигу и сохраняем её. Вторым проходом мы получаем готовые кадры, подставив вместо каждого блока индекс наиболее похожего из книги.
Крутим размер книжки и блока, по соотношению качества и степени сжатия выбираем блок 8x4 и размер книги в… 128 блоков, то есть 128x8x4 = 4096 бит, или 512 байт. То есть, мы заменяем 32 бита на 7, сдувая кадр минимум в четыре раза.
(Напомню, мы к тому же шерстим не кадры целиком, а только разницу между ними.)
Вооружённые всем вышесказанным, переписываем упаковщик, перебираемся с питона на асм и начинаем пробовать всю эту богадельню распаковать. Распаковщик описывать особого смысла нет, всё тривиально.
В сущности, выходит некое подобие какого-то странного текстового режима. Однако сжатие радует: около 200 байт на кадр!

Хозяйке на заметку: ла***ны

В столь полюбившейся почтенной публике работе мы тихонько спойлернули и сделали именно это: общая для 52 картинок кодовая книга со 128 блоками размером 8x2, zx7 с чуток докрученным депакером сверху — и, на минуточку, овер 350К запихиваются в стандартные 128!
На фоне всеобщего разгула страстей это замечено не было.
Как, впрочем, и ожидалось.

Make it sing
Тем временем хмурые октябрьские ночи внезапно перетекли в декабрьские, а потом, уж совсем неожиданно, и в январские. Дела, работа, семья… repeat. Но у нас уже есть собранная TRD-шка. Пока что без звука и только для Pentagon 512K+. Видео занимает около 400К. Меньше можно, но нельзя: либо качество падает, либо начинает страдать несварением ЦПУ бедный распаковщик.
Великолепный Олег тактично интересуется, не пора ли уже и ему взорвать тишину, в конце-то концов? Уточняет, насколько у него связаны руки в плане памяти и наворотов. Я, как поклонник турбосаунда, думаю не особо долго. Очень хочу Bad Apple в два чипа! Под такое дело и двух банок в нижней памяти не жалко, чёрт возьми!

Хозяйке на заметку: TurboSound
Не понимаю, как эта чудесная, простая и эффектная доработка до сих пор не стала стандартом де факто. Не понимаю. Нет. Ой, всё.


Расшариваем папочку в дропбоксе, и Олежа начинает священнодействие.
Я получаю двойное удовольствие: Олег сохраняет последовательные шаги в музыке так, как это обычно делают в графике. У меня есть редкая возможность слышать и наблюдать, как трек изменяется, крепнет, «обрастая» звуковым «мясом». Бесценно!
Кресло, на котором я подпрыгиваю в наушниках, близко к терминальной стадии разрушения.
Всплывает маленькая проблемка, о которой мы и до того подозревали: скорость TS-трека немного расходится со скоростью оригинальной звуковой дорожки, он несколько быстрее. Без танцев с бубном и дёрганьем трек не вытянуть, да и не нужно — прикинув разницу и поменяв одну строчку в упаковщике, чуть ускоряю видео — до 103.5% от оригинала. make trd, enter, готово. make run. Есть синхра! :)
Следующая проблемка — потребление тактов плеером TS. Как это ни смешно, видео приходится даже немножечко разжать :) Итоговый размер — около 430К.
И вот звучат фанфары: трек полностью закончен! Лихой задор! Кресло доломано бесповоротно.
frames: ~2100
raw zx screens size: ~13M
avg bytes per frame: ~200
compression ratio: ~29.2:1
packed size: ~430K

Кушать подано!
Трек торчит на repeat-е энные сутки. NES-версия изменившимся лицом бежит нервно курить пруду, а соседи, семья и домашние животные от безысходности начинают разучивать слова. Думаем, как лучше зарелизить вот это вот всё.
Идея выпуститься под старым добрым лейблом Techno Lab пришла ещё во время просмотра отличной новогодней интры by Placebo, автор которой, к тому же, любезно соглашается помочь с оформлением релиза и рисует отпадный ASCII-art. Спасибо, dman_pcb !



Изначально собираемся выложить просто так, для исправления исторической несправедливости.
Тут проходит инфа о предстоящем ZX Enhanced компо. Кажется, трудно придумать вариант лучше!
Опять же, были некоторые сомнения: не зарелизить ли out of compo? Как-никак, у нас не вполне дема! Но, учитывая активность сцены, всё-таки кладу наше яблоко в корзинку с демами. Пусть будет :)
Перед этим нахожу хороший менеджер верхней памяти под всё на свете (затрудняюсь точно указать авторство, там человека три минимум), немного правлю под свои нужды и приделываю вместо своей пентагон-only листалки страниц.
Всё, можно расслабиться. Что было дальше, вы и так знаете.
Большое спасибо организаторам пати и всем, кто помогал нам словом и делом.
Надеемся, плохое яблоко всё же вышло не таким уж и плохим :)



До скорого, друзья! Жмите демосцены!

nagareteku toki no naka de demo
kedarusa ga hora guruguru mawatte
и немедленно выпил
(И. Бродский)

(end-of-frame)

44 комментария

avatar
avatar
Спасибо за магию kowalski ! Выпил стоя!
avatar
Спасибо за правильное видео! :)
avatar
я немножко позанудничаю: яблоки были хорошо пригодны для потребления без обработки. В смысле оригинальный ролик был хорош именно своей пиксельностью и гладкостью. Для девичьих фигур гладкость особенно важна ;) и я также давно мечтал чтобы эту анимацию реализовали на Спектруме.
к сожалению яблочки изрядно сморщились при ужатии. был бы оригинал болеее дерганный и в другой изобразительном исполнении то эта техника сжатия принесла бы еще большую эффективность.
a badapple лучьшеб реализовать на движке от Jozin…
avatar
Вот уж не думал, что есть кто-то более занудный, чем я )))
avatar
Конечно, можно и с SD-карты читать, но это была бы совсем другая история. И другая, современная платформа.
Мне было интересно постараться сделать так, чтобы можно было посмотреть на расширенном, но старом железе — АТМках, Скорпах, Пентагонах, Каях, Профиках.
Когда DimkaM допилит свой движок, посмотреть на нём Bad Apple будет проще пареной репы.
А пока — как любит говорить JV-Soft, арфы нет, возьмите бубен :)
avatar
«DimkaM by Jozin»
avatar
Н — наука!
avatar
Вы же понимаете, на самом-то деле я — не настоящий сварщик. Просто мимо проходил :)
avatar
«В столь полюбившейся почтенной публике работе мы тихонько спойлернули и сделали именно это: общая для 52 картинок кодовая книга...»

Гиперссылку дайте там по тексту на видео — через пару месяцев уже никто не разберет о чем речь.
И я правильно понимаю, что в случае с Ленинградом (по крайне мере начало клипа) сработала история что кадры с одной точки сняты — соответственно от кадра к кадру изменения не значительны. История с прыжками на кровати там особо мне понравилась в этой реализации.
avatar
Сделано!
На самом деле разницы кадров там не было смысла брать, уж очень они разные. Поэтому кадры там целиком, а с прыжками на кровати просто удачно совпало :)
avatar
Эта работа ценна своей технической составляющей, а не визуально-эстетической — этот момент критики не берут в расчет. Там могло быть, что угодно, кроме плохого яблока, и дема была бы не менее ценна. Впрочем, автор, кажется, и так всё сказать в статье.
  • sq
  • +4
avatar
вот ведь и согласен и задаю таки вопрос. реторический: чегож не взяли это «что угодно» то :)
avatar
Пегий дудочник, залогинься, мы тебя узнали!
  • sq
  • +1
avatar
Отличная статья!
Отличный релиз.
avatar
Спасибо! )
avatar
Для любопытствующих рипперов HQ контента со стажет 10+ лет: собрать версию под 1М и визуально сравнить качество.
  • tsl
  • +3
avatar
Теоретически можно влезть в этот размер без потерь, но с теми же 10 fps.
Вопрос хранения метрового файла остаётся открытым :)
avatar
а можно запхать в четырехметровый SPG и сделать версию под тсконфу с 320x240 и звуком на цовоксе ;)
avatar
C64 подтягивает данные с дисковода, может быть и на спектруме можно было бы? 640Кб как никак и показывать на обычном 128к. Или не успеть распаковать и показать такие кадры?

Раз уж качество всё равно lossy, то интересно, что можно было бы ужать в 128к, какой размер кадра и чанка был бы при приемлемом фреймрейте.

Пусть и не полноценное демо, но релиз отличный! Еще один «видеокодек», отличнейшая музыка и прекрасные ANSI!
avatar
Спасибо! ) Мне очень хотелось, чтобы всё работало на 128, но я не осилил. Видео такого размера чисто в 128К получается совершенно несмотрибельным (даже без музыки). Пытался разобраться с тем, как отцы ходили в TR-DOS при включенных прерываниях, читал исходники мультилоадеров, но чуял, что не смогу стримить — не успею. (Это не значит, что нельзя в принципе, говорю только за себя — а руки у меня кривоваты). К тому же я как-то побаиваюсь трюков с нестандартными прыжками в TR-DOS и прямым программированием ВГ93.
avatar
Офигенно! Особенно порадовал трек! Очень классно получилось! Спасибо n1k-o & kowalski.

По поводу паковки чёрно-белых картинок, в своё время разговаривал с Flying и он мне рассказывал, что их последней работе Jam (где моушин сакс и танцующий Нунзен :) реализован очень офигенный алгоритим сжатия ч/б картинок, который позволяет запихнуть в 128к достаточно большой объём.



Может стоит покопать в эту сторону? Ну или хотя б поговорить с Flying'ом может что из заготовок осталось живое?
avatar
Для того чтобы получить более высокое качество при том же количестве кадров, как сейчас в bad apple у kowalski, нужен действительно какой-то другой прорывной алогоритм.
avatar
все есть в журнале сценерджи. я в эпоху запиляторства смотрел все аниматорщики на спектруме которые сыскал. Hrumer сделал например — неплохо жал.
avatar
И заметьте! видео сделали свое оригинальное. а не надергали с тырнетов (в том году это, между прочим, моветоном было :) )
Думаеться мне анимации такого вида, с плясками, раттерновыми движеами пожмуться более эффективно.
avatar
Тоже год назад, это яблоко мучал…
В итоге 5мб (оптимизированный *.gif) c максимально возможным качеством и всеми (не пропущенными) кадрами.

www.dropbox.com/s/u6bxu59arr1obj8/BA_test_2.gif?dl=0

Уверен, что еще сильнее можно ужать, но как мудро замечено выше, Z80 на лету это просто не осилит.
avatar
Офигенно!
avatar
Обычный не осилит, а расширенный — осилит.
avatar
воооо! круто. уже все подготовлено. предайте ДимкеМ.
avatar
А поделишься скринами-кадрами с нужным fps? Хочу поиграться, насколько VideoStudio сможет утрамбовать.
avatar
Год назад же баловался, потер уже давно.
Как вариант раздербанить готовую гифку, на отдельные кадры.

Вот эта вроде без оптимизации: www.dropbox.com/s/lm1p7esgodpndfg/BA_test.gif?dl=0

Ну а fps ты уже сам на глаз ставь, как нужно. Либо 60(в оригинале скорее всего именно столько), либо 30.
avatar
Слишком жирная гифка, а конвертер не поддерживает пропуск кадров.
Нашел другой кусок на 360 кадров, удалось сжать в 10 раз при сравнимом качестве и 10fps. И это на реале в программе более чем 10-летней давности:) Так что это прекрасно, что технологии не стоят на месте.
avatar
А можно ли где-нибудь почитать про приёмы, использущиеся в VideoStudio?
Всё то, что происходило на платформе с конца девяностых, для меня is the new new.
avatar
Конечно. Смотри ныне почивший сайт zxvideo.fatal.ru за авторством Rasmer, где есть несколько моих статей:

web.archive.org/web/20150501193635/http://zxvideo.fatal.ru/
avatar
надо бы ему просигналить пусть полный архив сайта выложит чтоли
avatar
только что имел говорить с расмером — архива сайта нет, увы
avatar
Прочёл, спасибо, здорово! Получается, до какого-то момента я шёл практически след в след :) Забавно, что отбрасывание блоков, изменившихся меньше определённого порога — это как раз то, чем мне в конце пришлось пожертвовать ради скорости.
Жалко, что ресурс приказал долго жить. Эх, собрал бы кто всё разбросанное по интернетам под одной крышей…
avatar
На контрастных видео это отбрасывание приводило к артефактам — за движущимися объектами оставался хвост. Фиксилось оптимизацией, которая обнуляла почти пустые знакоместа и заполняла почти полные.
Идею со словарем одно время реализовывал Alco- был набор из 256 гвоздями прибитых знакомест, к которым приводился экран и хранились чисто индексы. Очень эффективно, но очень грубо в результате. Другая его идея была в составлении 256 самых частых пар байт, которые можно опять же свести к индексу в таблице, насколько я помню, не взлетело.
Еще я пробовал реюзать знакоместа на экране — ооочень медленно получалось паковать на реале, но выигрыш был на уровне пары процентов, потому закопал.
avatar
Ага, мусор был, обходил это дело. Реюзать знакоместа — это в смысле использовать уже распакованные в качестве словаря, как в LZчтототам? Такое у меня тоже не дало хорошего результата. 256 блоков 8x8 — да, получается совсем худо. Можно улучшить, если есть вариант брать не один словарь на все кадры, а каждые N кадров свой, как у Алгоритма сделано.
avatar
Упс. Не заметил коммента.
Реюзать знакоместа — это в смысле использовать уже распакованные в качестве словаря, как в LZчтототам?
Да. Смотреть с некоторым окном назад и вместо знакоместа хранить ссылку.

Кстати о птичках, ты пробовал свой компрессор натравить на картинки, сконверченные по методу ordered? Это которые с текстурами как чанки.
avatar
Вот, попробовал пожевать ordered 4x4 с теми же параметрами, что и в лабутенах:



Теряются мелкие детали — словарь же формируется тупо в лоб, по частоте, и они просто выпадают. Было бы визуально интереснее пожертвовать заливкой фона ради деталей, но для этого надо хороший алгоритм построения словаря писать )
avatar
Виноват, скукожилось при загрузке. Вот в оригинальном размере.
avatar
Ну потери качества они по любому будут. Интересует какой уровень сжатия получится.

А что думаешь по поводу динамического словаря? Т.е. выбирать самые частотные блоки не на всей выборке кадров, а локальные максимумы.
avatar
Если словарь будет меняться каждые несколько кадров, тогда может получиться. Я не пробовал, ориентировался на один словарь изначально. Вообще, думаю, идея строить его по частоте сама по себе не очень. Наверное, если попробовать размазывать ошибку по кадру, будет получше. Кстати, algorithm вроде бы собирался релизить свою тулзу с правильным vq, генетикой и прочим блэкджеком, интересно было бы потрогать.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.